[FEAT] 實作角色權限分類、租戶角控管理與介面多語系優化
1. [FEAT] 權限劃分為「系統層級」與「客戶層級」,並在後端強制過濾跨權限分配。 2. [FEAT] 整合選單權限至主選單層級 (基本設定、權限設定),簡化角色管理 UI。 3. [STYLE] 側邊欄優化:補齊多語系翻譯,並為基本設定子選單增加視覺圖示。 4. [REFACTOR] 更新 RoleSeeder,將 tenant-admin 重新分類為客戶層級角色。
This commit is contained in:
@@ -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
|
||||
|
||||
49
resources/views/components/luxury-time-input.blade.php
Normal file
49
resources/views/components/luxury-time-input.blade.php
Normal 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>
|
||||
Reference in New Issue
Block a user