[DOCS] 更新 RBAC 實作規範與角色初始化流程建議
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 55s

This commit is contained in:
2026-03-19 17:18:21 +08:00
parent 5548bb1cc9
commit f00fc940a9
13 changed files with 474 additions and 74 deletions

View File

@@ -7,28 +7,53 @@
@section('content')
<div class="space-y-6" x-data="{
showModal: false,
editing: false,
showModal: {{ $errors->any() ? 'true' : 'false' }},
editing: {{ old('_method') === 'PUT' || (isset($user) && $errors->any()) ? 'true' : 'false' }},
allRoles: @js($roles),
currentUser: {
id: '',
name: '',
username: '',
email: '',
phone: '',
company_id: '',
role: 'user',
status: 1
id: '{{ old('id') }}',
name: '{{ old('name') }}',
username: '{{ old('username') }}',
email: '{{ old('email') }}',
phone: '{{ old('phone') }}',
company_id: '{{ old('company_id', auth()->user()->isSystemAdmin() ? '' : auth()->user()->company_id) }}',
role: '{{ old('role', '') }}',
status: {{ old('status', 1) }}
},
get filteredRoles() {
if (this.currentUser.company_id === '' || this.currentUser.company_id === null) {
// 系統層級:顯示 is_system = 1 的角色
return this.allRoles.filter(r => r.is_system);
} else {
// 客戶層級:只顯示該公司的角色
let roles = this.allRoles.filter(r => r.company_id == this.currentUser.company_id);
// 如果是系統管理員,額外允許選擇「客戶層級範本」
@if(auth()->user()->isSystemAdmin())
let templates = this.allRoles.filter(r => !r.is_system && (r.company_id === null || r.company_id === ''));
roles = [...roles, ...templates];
@endif
return roles;
}
},
openCreateModal() {
this.editing = false;
this.currentUser = { id: '', name: '', username: '', email: '', phone: '', company_id: '', role: 'user', status: 1 };
this.currentUser = { id: '', name: '', username: '', email: '', phone: '', company_id: '{{ auth()->user()->isSystemAdmin() ? "" : auth()->user()->company_id }}', role: '', status: 1 };
this.showModal = true;
// 預設選取第一個可用的角色
this.$nextTick(() => {
if (this.filteredRoles.length > 0) {
this.currentUser.role = this.filteredRoles[0].name;
}
});
},
openEditModal(user) {
this.editing = true;
this.currentUser = {
...user,
role: user.roles && user.roles.length > 0 ? user.roles[0].name : 'user'
company_id: user.company_id || '',
role: user.roles && user.roles.length > 0 ? user.roles[0].name : ''
};
this.showModal = true;
}
@@ -217,61 +242,97 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Full Name') }}</label>
<input type="text" name="name" x-model="currentUser.name" required class="luxury-input" placeholder="{{ __('e.g. John Doe') }}">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">
{{ __('Full Name') }} <span class="text-rose-500">*</span>
</label>
<input type="text" name="name" x-model="currentUser.name" required class="luxury-input @error('name') border-rose-500 @enderror" placeholder="{{ __('e.g. John Doe') }}">
@error('name')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Username') }}</label>
<input type="text" name="username" x-model="currentUser.username" required class="luxury-input" placeholder="{{ __('e.g. johndoe') }}">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">
{{ __('Username') }} <span class="text-rose-500">*</span>
</label>
<input type="text" name="username" x-model="currentUser.username" required class="luxury-input @error('username') border-rose-500 @enderror" placeholder="{{ __('e.g. johndoe') }}">
@error('username')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Email') }}</label>
<input type="email" name="email" x-model="currentUser.email" class="luxury-input" placeholder="{{ __('john@example.com') }}">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">
{{ __('Email') }} <span class="text-rose-500">*</span>
</label>
<input type="email" name="email" x-model="currentUser.email" required class="luxury-input @error('email') border-rose-500 @enderror" placeholder="{{ __('john@example.com') }}">
@error('email')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Phone') }}</label>
<input type="text" name="phone" x-model="currentUser.phone" class="luxury-input">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Role') }}</label>
<select name="role" x-model="currentUser.role" class="luxury-select">
@foreach($roles as $role)
<option value="{{ $role->name }}">{{ __($role->name) }}</option>
@endforeach
</select>
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Status') }}</label>
<select name="status" x-model="currentUser.status" class="luxury-select">
<option value="1">{{ __('Active') }}</option>
<option value="0">{{ __('Disabled') }}</option>
</select>
<input type="text" name="phone" x-model="currentUser.phone" class="luxury-input @error('phone') border-rose-500 @enderror">
@error('phone')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
</div>
@if(auth()->user()->isSystemAdmin())
<div class="space-y-2">
<div class="space-y-2 mb-6">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Company') }}</label>
<select name="company_id" x-model="currentUser.company_id" class="luxury-select">
<select name="company_id" x-model="currentUser.company_id" class="luxury-select @error('company_id') border-rose-500 @enderror"
@change="$nextTick(() => { if (filteredRoles.length > 0 && !filteredRoles.find(r => r.name === currentUser.role)) { currentUser.role = filteredRoles[0].name; } })">
<option value="">{{ __('SYSTEM') }}</option>
@foreach($companies as $company)
<option value="{{ $company->id }}">{{ $company->name }}</option>
@endforeach
</select>
@error('company_id')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
@endif
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">
{{ __('Role') }} <span class="text-rose-500">*</span>
</label>
<select name="role" x-model="currentUser.role" class="luxury-select @error('role') border-rose-500 @enderror">
<template x-for="role in filteredRoles" :key="role.id">
<option :value="role.name" x-text="role.name"></option>
</template>
</select>
@error('role')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Status') }}</label>
<select name="status" x-model="currentUser.status" class="luxury-select @error('status') border-rose-500 @enderror">
<option value="1">{{ __('Active') }}</option>
<option value="0">{{ __('Disabled') }}</option>
</select>
@error('status')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">
<span x-text="editing ? '{{ __('New Password (leave blank to keep current)') }}' : '{{ __('Password') }}'"></span>
<template x-if="!editing">
<span class="text-rose-500">*</span>
</template>
</label>
<input type="password" name="password" :required="!editing" class="luxury-input" placeholder="••••••••">
<input type="password" name="password" :required="!editing" class="luxury-input @error('password') border-rose-500 @enderror" placeholder="••••••••">
@error('password')
<p class="text-[10px] font-bold text-rose-500 mt-1 pl-1 uppercase tracking-tight">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end gap-x-4 pt-8">