[FEAT] 優化部署流程:加入 RoleSeeder 與 AdminUserSeeder,並實作權限系統基礎架構與多租戶隔離機制
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 48s

This commit is contained in:
2026-03-13 17:35:22 +08:00
parent 39d25ed1d4
commit 56daf8940b
41 changed files with 3052 additions and 358 deletions

View File

@@ -52,113 +52,136 @@
$links[] = $foundModule;
}
// 2. 處理具體頁面
// 2. 處理中間層級與具體頁面
$segments = explode('.', $routeName);
$lastSegment = end($segments);
// 嘗試翻譯最後一個片段作為頁面名稱
$pageLabel = match($lastSegment) {
'edit' => __('Profile'), // 專門處理 profile.edit
'index' => null, // 通常 index 代表列表,如果在大模組下則不重複顯示
'logs' => __('Machine Logs'),
'permissions' => __('Machine Permissions'),
'utilization' => __('Utilization Rate'),
'expiry' => __('Expiry Management'),
'maintenance' => __('Maintenance Records'),
'ui-elements' => __('UI Elements'),
'helper' => __('Helper'),
'questionnaire' => __('Questionnaire'),
'games' => __('Games'),
'timer' => __('Timer'),
'personal' => __('Warehouse List (Individual)'),
'stock-management' => __('Stock Management'),
'transfers' => __('Transfers'),
'purchases' => __('Purchases'),
'replenishments' => __('Replenishments'),
'replenishment-records' => __('Replenishment Records'),
'machine-stock' => __('Machine Stock'),
'staff-stock' => __('Staff Stock'),
'returns' => __('Returns'),
'pickup-codes' => __('Pickup Codes'),
'orders' => __('Orders'),
'promotions' => __('Promotions'),
'pass-codes' => __('Pass Codes'),
'store-gifts' => __('Store Gifts'),
'change-stock' => __('Change Stock'),
'machine-reports' => __('Machine Reports'),
'product-reports' => __('Product Reports'),
'survey-analysis' => __('Survey Analysis'),
'products' => __('Product Management'),
'advertisements' => __('Advertisement Management'),
'admin-products' => __('Admin Sellable Products'),
'accounts' => __('Account Management'),
'sub-accounts' => __('Sub Accounts'),
'sub-account-roles' => __('Sub Account Roles'),
'points' => __('Point Settings'),
'badges' => __('Badge Settings'),
'restart' => __('Machine Restart'),
'restart-card-reader' => __('Card Reader Restart'),
'checkout' => __('Remote Checkout'),
'lock' => __('Remote Lock'),
'change' => __('Remote Change'),
'dispense' => __('Remote Dispense'),
'official-account' => __('Line Official Account'),
'coupons' => __('Line Coupons'),
'stores' => __('Store Management'),
'time-slots' => __('Time Slots'),
'venues' => __('Venue Management'),
'reservations' => __('Reservations'),
'clear-stock' => __('Clear Stock'),
'apk-versions' => __('APK Versions'),
'discord-notifications' => __('Discord Notifications'),
'app-features' => __('APP Features'),
'roles' => __('Roles'),
'others' => __('Others'),
'ai-prediction' => __('AI Prediction'),
'create' => __('Create'),
'show' => __('Show'),
'members' => __('Member List'), // 處理 admin.members.index 這種情況
default => null,
};
// 如果匹配不到,嘗試處理一些特殊的 index 標籤
if (!$pageLabel && $lastSegment === 'index') {
$pageLabel = match($segments[count($segments)-2] ?? '') {
// 如果路由有四段以上 (如 admin.permission.companies.index),則處理中間一段
if (count($segments) >= 4) {
$midSegment = $segments[2];
$midLabel = match($midSegment) {
'companies' => __('Customer Management'),
'members' => __('Member List'),
'machines' => __('Machine List'),
'warehouses' => __('Warehouse List'),
'sales' => __('Sales Records'),
default => null,
};
if ($midLabel) {
$links[] = [
'label' => $midLabel,
'url' => '#', // 如果有需要可以導向 index 路由
'active' => $lastSegment === 'index'
];
}
}
if ($pageLabel) {
$links[] = [
'label' => $pageLabel,
'url' => route($routeName),
'active' => true
];
// 3. 處理最後一個動作/頁面
if ($lastSegment !== 'index') {
$pageLabel = match($lastSegment) {
'edit' => __('Edit'),
'create' => __('Create'),
'show' => __('Detail'),
'logs' => __('Machine Logs'),
'permissions' => __('Machine Permissions'),
'utilization' => __('Utilization Rate'),
'expiry' => __('Expiry Management'),
'maintenance' => __('Maintenance Records'),
'ui-elements' => __('UI Elements'),
'helper' => __('Helper'),
'questionnaire' => __('Questionnaire'),
'games' => __('Games'),
'timer' => __('Timer'),
'personal' => __('Warehouse List (Individual)'),
'stock-management' => __('Stock Management'),
'transfers' => __('Transfers'),
'purchases' => __('Purchases'),
'replenishments' => __('Replenishments'),
'replenishment-records' => __('Replenishment Records'),
'machine-stock' => __('Machine Stock'),
'staff-stock' => __('Staff Stock'),
'returns' => __('Returns'),
'pickup-codes' => __('Pickup Codes'),
'orders' => __('Orders'),
'promotions' => __('Promotions'),
'pass-codes' => __('Pass Codes'),
'store-gifts' => __('Store Gifts'),
'change-stock' => __('Change Stock'),
'machine-reports' => __('Machine Reports'),
'product-reports' => __('Product Reports'),
'survey-analysis' => __('Survey Analysis'),
'products' => __('Product Management'),
'advertisements' => __('Advertisement Management'),
'admin-products' => __('Admin Sellable Products'),
'accounts' => __('Account Management'),
'sub-accounts' => __('Sub Accounts'),
'sub-account-roles' => __('Sub Account Roles'),
'points' => __('Point Settings'),
'badges' => __('Badge Settings'),
'restart' => __('Machine Restart'),
'restart-card-reader' => __('Card Reader Restart'),
'checkout' => __('Remote Checkout'),
'lock' => __('Remote Lock'),
'change' => __('Remote Change'),
'dispense' => __('Remote Dispense'),
'official-account' => __('Line Official Account'),
'coupons' => __('Line Coupons'),
'stores' => __('Store Management'),
'time-slots' => __('Time Slots'),
'venues' => __('Venue Management'),
'reservations' => __('Reservations'),
'clear-stock' => __('Clear Stock'),
'apk-versions' => __('APK Versions'),
'discord-notifications' => __('Discord Notifications'),
'app-features' => __('APP Features'),
'roles' => __('Roles'),
'others' => __('Others'),
'ai-prediction' => __('AI Prediction'),
'data-config' => __('Data Configuration Permissions'),
'sales' => __('Sales Permissions'),
'machines' => __('Machine Management Permissions'),
'warehouses' => __('Warehouse Permissions'),
'analysis' => __('Analysis Permissions'),
'audit' => __('Audit Permissions'),
'remote' => __('Remote Permissions'),
'line' => __('Line Permissions'),
default => null,
};
if ($pageLabel) {
$links[] = [
'label' => $pageLabel,
'url' => route($routeName),
'active' => true
];
}
}
// 確保最後一個 link 是 active 的
if (!empty($links)) {
$links[count($links) - 1]['active'] = true;
// 如果倒數第二個也是同個頁面(例如 Dashboard > Dashboard),則移除重複
// 檢查是否有相鄰重複的 Label (例如 Dashboard > Dashboard)
if (count($links) > 1 && $links[count($links)-1]['label'] === $links[count($links)-2]['label']) {
array_pop($links);
$links[count($links)-1]['active'] = true;
}
}
}
}
@endphp
<nav {{ $attributes->merge(['class' => 'flex', 'aria-label' => 'Breadcrumb']) }}>
<ol class="flex items-center whitespace-nowrap min-w-0 w-full">
@foreach($links as $link)
<li class="flex items-center text-sm {{ $link['active'] ? 'font-semibold text-slate-800 dark:text-slate-200' : 'text-slate-500 dark:text-slate-400' }} {{ !$loop->last ? 'shrink-0' : 'truncate' }}">
@if(!$link['active'] && $link['url'] !== '#')
<a class="hover:text-cyan-600 transition-colors" href="{{ $link['url'] }}">
@php
$isActive = $link['active'] ?? false;
$url = $link['url'] ?? '#';
@endphp
<li class="flex items-center text-sm {{ $isActive ? 'font-semibold text-slate-800 dark:text-slate-200' : 'text-slate-500 dark:text-slate-400' }} {{ !$loop->last ? 'shrink-0' : 'truncate' }}">
@if(!$isActive && $url !== '#')
<a class="hover:text-cyan-600 transition-colors" href="{{ $url }}">
{{ $link['label'] }}
</a>
@else