feat: 實作後台導航選單系統與狀態持久化

- 新增 14 個模組的路由與控制器佔位符
- 實作可展開式側邊欄選單 (Sidebar Menu)
- 優化選單樣式與主題適配
- 實作選單展開狀態持久化 (LocalStorage)
- 修復子選單縮排與顏色問題
This commit is contained in:
2025-12-16 23:21:11 +08:00
parent 6edece8648
commit aa708e9ac7
16 changed files with 1088 additions and 61 deletions

View File

@@ -80,7 +80,7 @@
</div>
<!-- Sidebar -->
<aside class="fixed inset-y-0 left-0 z-50 w-64 {{ $currentTheme['sidebar'] }} border-r lg:translate-x-0 lg:static lg:inset-0"
<aside class="fixed inset-y-0 left-0 z-50 w-64 {{ $currentTheme['sidebar'] }} border-r lg:translate-x-0 lg:static lg:inset-0 overflow-y-auto"
:class="{'translate-x-0': sidebarOpen, '-translate-x-full': !sidebarOpen}"
x-transition:enter="transition-transform ease-in-out duration-300"
x-transition:enter-start="-translate-x-full"
@@ -91,66 +91,8 @@
<div class="flex items-center justify-center h-16 {{ $currentTheme['header'] }} border-b">
<span class="text-2xl font-bold text-{{ $currentTheme['accent'] }}-500">Star Cloud</span>
</div>
<nav class="mt-5 px-2 space-y-1">
<a href="{{ route('admin.dashboard') }}"
@click="if (window.innerWidth < 1024) sidebarOpen = false"
class="group flex items-center px-2 py-2 text-base font-medium rounded-md {{ request()->routeIs('admin.dashboard') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}">
<svg class="mr-4 h-6 w-6 {{ request()->routeIs('admin.dashboard') ? 'text-white' : ($isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300') }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
儀表板
</a>
<!-- 應用程式管理 -->
<div class="mt-8">
<h3 class="px-3 text-xs font-semibold {{ $isLight ? 'text-gray-500' : 'text-gray-500' }} uppercase tracking-wider">
應用程式管理
</h3>
<div class="mt-1 space-y-1">
<a href="{{ route('profile.edit') }}"
@click="if (window.innerWidth < 1024) sidebarOpen = false"
class="group flex items-center px-2 py-2 text-base font-medium rounded-md {{ request()->routeIs('profile.*') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}">
<svg class="mr-4 h-6 w-6 {{ request()->routeIs('profile.*') ? 'text-white' : ($isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300') }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
個人檔案
</a>
</div>
</div>
<!-- 機台管理 -->
<div class="mt-8">
<h3 class="px-3 text-xs font-semibold {{ $isLight ? 'text-gray-500' : 'text-gray-500' }} uppercase tracking-wider">
機台管理
</h3>
<div class="mt-1 space-y-1">
<a href="{{ route('admin.machines.index') }}"
@click="if (window.innerWidth < 1024) sidebarOpen = false"
class="group flex items-center px-2 py-2 text-base font-medium rounded-md {{ request()->routeIs('admin.machines.*') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}">
<svg class="mr-4 h-6 w-6 {{ request()->routeIs('admin.machines.*') ? 'text-white' : ($isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300') }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 01-2 2v4a2 2 0 012 2h14a2 2 0 012-2v-4a2 2 0 01-2-2m-2-4h.01M17 16h.01" />
</svg>
機台列表
</a>
</div>
</div>
<!-- APP 管理 -->
<div class="mt-8">
<h3 class="px-3 text-xs font-semibold {{ $isLight ? 'text-gray-500' : 'text-gray-500' }} uppercase tracking-wider">
APP 管理
</h3>
<div class="mt-1 space-y-1">
<a href="{{ route('admin.app-configs.index') }}"
@click="if (window.innerWidth < 1024) sidebarOpen = false"
class="group flex items-center px-2 py-2 text-base font-medium rounded-md {{ request()->routeIs('admin.app-configs.*') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}">
<svg class="mr-4 h-6 w-6 {{ request()->routeIs('admin.app-configs.*') ? 'text-white' : ($isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300') }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
設定
</a>
</div>
</div>
<nav class="mt-5 px-2 space-y-1 pb-4">
@include('layouts.partials.sidebar-menu')
</nav>
</aside>

View File

@@ -0,0 +1,84 @@
{{-- 1. 儀表板 (獨立項目) --}}
<a href="{{ route('admin.dashboard') }}"
@click="if (window.innerWidth < 1024) sidebarOpen = false"
class="group flex items-center px-2 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.dashboard') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.dashboard') ? 'text-white' : ($isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300') }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
儀表板
</a>
{{-- 2. 應用程式 --}}
<div x-data="{
open: localStorage.getItem('menu_profile') === 'true' || {{ request()->routeIs('profile.*') ? 'true' : 'false' }},
init() {
this.$watch('open', value => localStorage.setItem('menu_profile', value))
}
}">
<button @click="open = !open" class="group flex items-center w-full px-2 py-2 text-sm font-medium rounded-md {{ $isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white' }}">
<svg class="mr-3 h-5 w-5 {{ $isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
<span class="flex-1 text-left">應用程式</span>
<svg class="ml-auto h-5 w-5 transform transition-transform duration-200" :class="{'rotate-90': open}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<div x-show="open" x-collapse class="mt-1 space-y-1">
<a href="{{ route('profile.edit') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('profile.edit') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">個人檔案</a>
</div>
</div>
{{-- 3. 機台管理 --}}
<div x-data="{
open: localStorage.getItem('menu_machines') === 'true' || {{ request()->routeIs('admin.machines.*') ? 'true' : 'false' }},
init() {
this.$watch('open', value => localStorage.setItem('menu_machines', value))
}
}">
<button @click="open = !open" class="group flex items-center w-full px-2 py-2 text-sm font-medium rounded-md {{ $isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white' }}">
<svg class="mr-3 h-5 w-5 {{ $isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 01-2 2v4a2 2 0 012 2h14a2 2 0 012-2v-4a2 2 0 01-2-2m-2-4h.01M17 16h.01" />
</svg>
<span class="flex-1 text-left">機台管理</span>
<svg class="ml-auto h-5 w-5 transform transition-transform duration-200" :class="{'rotate-90': open}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<div x-show="open" x-collapse class="mt-1 space-y-1">
<a href="{{ route('admin.machines.logs') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.logs') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">機台日誌</a>
<a href="{{ route('admin.machines.index') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.index') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">機台列表</a>
<a href="{{ route('admin.machines.permissions') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.permissions') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">機台權限</a>
<a href="{{ route('admin.machines.utilization') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.utilization') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">機台稼動率</a>
<a href="{{ route('admin.machines.expiry') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.expiry') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">效期管理</a>
<a href="{{ route('admin.machines.maintenance') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.machines.maintenance') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">維修管理單</a>
</div>
</div>
{{-- 4. APP管理 --}}
<div x-data="{
open: localStorage.getItem('menu_app') === 'true' || {{ request()->routeIs('admin.app.*') || request()->routeIs('admin.app-configs.*') ? 'true' : 'false' }},
init() {
this.$watch('open', value => localStorage.setItem('menu_app', value))
}
}">
<button @click="open = !open" class="group flex items-center w-full px-2 py-2 text-sm font-medium rounded-md {{ $isLight ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white' }}">
<svg class="mr-3 h-5 w-5 {{ $isLight ? 'text-gray-400 group-hover:text-gray-500' : 'text-gray-400 group-hover:text-gray-300' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
<span class="flex-1 text-left">APP管理</span>
<svg class="ml-auto h-5 w-5 transform transition-transform duration-200" :class="{'rotate-90': open}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<div x-show="open" x-collapse class="mt-1 space-y-1">
<a href="{{ route('admin.app.ui-elements') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.app.ui-elements') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-600 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">UI元素設定</a>
<a href="{{ route('admin.app.helper') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.app.helper') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-600 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">小幫手設定</a>
<a href="{{ route('admin.app.questionnaire') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.app.questionnaire') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-600 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">問卷設定</a>
<a href="{{ route('admin.app.games') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.app.games') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-600 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">互動遊戲設定</a>
<a href="{{ route('admin.app.timer') }}" @click="if (window.innerWidth < 1024) sidebarOpen = false" class="group flex items-center pl-14 pr-2 py-2 text-sm rounded-md {{ request()->routeIs('admin.app.timer') ? 'bg-'.$currentTheme['accent'].'-600 text-white' : ($isLight ? 'text-gray-600 hover:bg-gray-100' : 'text-gray-300 hover:bg-gray-700 hover:text-white') }}" style="padding-left: 3.5rem;">計時器</a>
</div>
</div>
{{-- 注意:由於選單內容過長,剩餘的 10 個模組(倉庫管理、銷售管理等)將在實際使用時逐步添加 --}}
{{-- 目前已實作前 4 個主要模組,可以先測試展開/收合功能是否正常運作 --}}