[FEAT] 實作角色權限分類、租戶角控管理與介面多語系優化

1. [FEAT] 權限劃分為「系統層級」與「客戶層級」,並在後端強制過濾跨權限分配。
2. [FEAT] 整合選單權限至主選單層級 (基本設定、權限設定),簡化角色管理 UI。
3. [STYLE] 側邊欄優化:補齊多語系翻譯,並為基本設定子選單增加視覺圖示。
4. [REFACTOR] 更新 RoleSeeder,將 tenant-admin 重新分類為客戶層級角色。
This commit is contained in:
2026-03-17 16:53:28 +08:00
parent 3ce88ed342
commit fc79148879
38 changed files with 2398 additions and 303 deletions

View File

@@ -33,6 +33,7 @@
'admin.reservation' => __('Reservation System'),
'admin.special-permission' => __('Special Permission'),
'admin.permission' => __('Permission Settings'),
'admin.basic-settings' => __('Basic Settings'),
];
// 1. 找出所屬大模組
@@ -62,7 +63,8 @@
$midLabel = match($midSegment) {
'companies' => __('Customer Management'),
'members' => __('Member List'),
'machines' => __('Machine List'),
'machines' => __('Machine Settings'),
'payment-configs' => __('Customer Payment Config'),
'warehouses' => __('Warehouse List'),
'sales' => __('Sales Records'),
default => null,
@@ -152,7 +154,6 @@
if ($pageLabel) {
$links[] = [
'label' => $pageLabel,
'url' => route($routeName),
'active' => true
];
}
@@ -189,7 +190,7 @@
@endif
@if(!$loop->last)
<svg class="flex-shrink-0 mx-3 overflow-visible h-2.5 w-2.5 text-slate-400 dark:text-slate-600" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg class="flex-shrink-0 mx-3 overflow-visible h-2.5 w-2.5 text-slate-500 dark:text-slate-400" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
@endif

View File

@@ -0,0 +1,49 @@
@props(['name', 'value' => '', 'label' => ''])
<div x-data="{
time: '{{ $value ? \Carbon\Carbon::parse($value)->format('H:i:s') : '' }}',
formatInput(e) {
let cursor = e.target.selectionStart;
let originalLen = e.target.value.length;
let val = e.target.value.replace(/\D/g, '');
if (val.length > 6) val = val.slice(0, 6);
let formatted = '';
if (val.length > 0) {
formatted = val.slice(0, 2);
if (val.length > 2) {
formatted += ':' + val.slice(2, 4);
if (val.length > 4) {
formatted += ':' + val.slice(4, 6);
}
}
}
this.time = formatted;
// Minor delay to fix cursor position if needed,
// though for time strings move-to-end is often acceptable.
this.$nextTick(() => {
let diff = formatted.length - originalLen;
let newPos = cursor + diff;
// e.target.setSelectionRange(newPos, newPos); // Optional cursor fix
});
}
}" class="relative">
<input
type="text"
name="{{ $name }}"
:value="time"
@input="formatInput"
maxlength="8"
placeholder="HH:mm:ss"
class="luxury-input w-full pr-12 font-mono tracking-wider"
autocomplete="off"
>
<div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400 dark:text-slate-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>