[FEAT] 完善全站多語系支援、角色權限篩選優化及 UI 元件重構
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m4s

- [DOCS] 補齊 en, ja, zh_TW 語系檔翻譯並完善驗證錯誤訊息 (validation.php)
- [FEAT] 角色權限頁面新增「所屬單位」篩選功能 (僅限系統管理員)
- [STYLE] 優化角色列表顯示,將「類型」變更為具體「所屬單位」名稱
- [STYLE] 修正角色頁面工具列佈局,搜尋框置前並修正下拉箭頭顯示
- [REFACTOR] 統一全站刪除確認視窗,導入新版 <x-delete-confirm-modal /> 組件
- [REFACTOR] 優化 PermissionController 查詢效能 (Eager Loading)
- [FIX] 修正 RoleSeeder 角色命名與資料庫同步邏輯
This commit is contained in:
2026-03-20 13:41:51 +08:00
parent 6588dcd7f7
commit d2cefe3f39
31 changed files with 2431 additions and 1775 deletions

View File

@@ -0,0 +1,60 @@
@props([
'message' => __('Are you sure you want to delete this item? This action cannot be undone.'),
'title' => __('Confirm Deletion')
])
<template x-teleport="body">
<div x-show="isDeleteConfirmOpen" class="fixed inset-0 z-[200] overflow-y-auto" x-cloak>
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div x-show="isDeleteConfirmOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity bg-slate-900/60 backdrop-blur-sm"
@click="isDeleteConfirmOpen = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">&#8203;</span>
<div x-show="isDeleteConfirmOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white dark:bg-slate-900 rounded-3xl shadow-2xl sm:my-8 sm:align-middle sm:max-w-md sm:w-full sm:p-8 border border-slate-100 dark:border-slate-800">
<div class="sm:flex sm:items-start text-center sm:text-left">
<div
class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-amber-100 dark:bg-amber-500/10 rounded-2xl sm:mx-0 sm:h-12 sm:w-12 text-amber-600 dark:text-amber-400">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 sm:mt-0 sm:ml-6">
<h3 class="text-xl font-black text-slate-800 dark:text-white leading-6 tracking-tight font-display uppercase">
{{ $title }}
</h3>
<div class="mt-4">
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 leading-relaxed">
{{ $message }}
</p>
</div>
</div>
</div>
<div class="mt-8 sm:mt-10 sm:flex sm:flex-row-reverse gap-3">
<form :action="deleteFormAction" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit"
class="inline-flex justify-center w-full px-6 py-3 text-sm font-black text-white transition-all bg-rose-500 rounded-xl hover:bg-rose-600 shadow-lg shadow-rose-200 dark:shadow-none hover:scale-[1.02] active:scale-[0.98] sm:w-auto uppercase tracking-widest font-display">
{{ __('Delete Permanently') }}
</button>
</form>
<button type="button" @click="isDeleteConfirmOpen = false"
class="inline-flex justify-center w-full px-6 py-3 mt-3 text-sm font-black text-slate-700 dark:text-slate-200 transition-all bg-slate-100 dark:bg-slate-800 rounded-xl hover:bg-slate-200 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto uppercase tracking-widest font-display">
{{ __('Cancel') }}
</button>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,52 @@
@php
$allErrors = [];
if (isset($errors)) {
foreach ($errors->getBags() as $bag) {
$allErrors = array_merge($allErrors, $bag->all());
}
}
@endphp
<div class="fixed top-8 left-1/2 -translate-x-1/2 z-[99999] w-full max-w-sm px-4 space-y-3 pointer-events-none">
@if(session('success'))
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 3000)"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(16,185,129,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-emerald-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-emerald-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="2.5" d="M5 13l4 4L19 7"/></svg>
</div>
<span>{{ session('success') }}</span>
</div>
@endif
@if(session('error') || count($allErrors) > 0)
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 5000)"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-rose-500/30 text-rose-600 dark:text-rose-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(244,63,94,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-rose-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-rose-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="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<span>
@if(session('error'))
{{ session('error') }}
@else
{{ $allErrors[0] ?? __('Please check the form for errors.') }}
@endif
</span>
</div>
@endif
</div>