@@ -1,10 +1,11 @@
import { useEffect , useMemo , useRef , useState } from 'react'
import { api , type PermissionDescriptor , type RoleRecord } from '../api'
import {
createRolePermissionDraft , updateRolePermission , validateRolePermissionDraft , visiblePatch ,
createRolePermissionDraft , rebaseRoleDraft , updateRolePermission , validateRolePermissionDraft , visiblePatch ,
type RolePermissionDraft , type RolePermissionValue ,
} from '../rolePermissions'
import { Collapsible , Icon } from '@ui'
import { useStore } from '../store'
export function filterRoles (
roles : RoleRecord [ ] , query : string , mode : 'exact' | 'fuzzy' , status : 'all' | 'enabled' | 'disabled' ,
@@ -46,6 +47,11 @@ function roleValue(role: RoleRecord, actionId: string): RolePermissionValue {
}
export function RolePermissionsPanel() {
const me = useStore ( state = > state . me )
const allowed = ( action : string ) = > ! ! me ? . isAdmin || hasSystemAction ( me ? . systemActions , action )
const canManageRoles = allowed ( 'system.roles.manage' )
const canViewPermissions = allowed ( 'system.permissions.view' )
const canManagePermissions = canViewPermissions && allowed ( 'system.permissions.manage' )
const [ roles , setRoles ] = useState < RoleRecord [ ] | null > ( null )
const [ catalog , setCatalog ] = useState < PermissionDescriptor [ ] > ( [ ] )
const [ selectedId , setSelectedId ] = useState ( '' )
@@ -56,38 +62,54 @@ export function RolePermissionsPanel() {
const [ mode , setMode ] = useState < 'exact' | 'fuzzy' > ( 'fuzzy' )
const [ status , setStatus ] = useState < 'all' | 'enabled' | 'disabled' > ( 'all' )
const [ group , setGroup ] = useState ( '' )
const [ actionFilter , setActionFilter ] = useState ( '' )
const [ roleCursor , setRoleCursor ] = useState ( '' )
const [ roleTrail , setRoleTrail ] = useState < string [ ] > ( [ ] )
const [ nextRoleCursor , setNextRoleCursor ] = useState ( '' )
const [ page , setPage ] = useState ( 0 )
const [ view , setView ] = useState < 'single' | 'matrix' > ( 'single' )
const [ newName , setNewName ] = useState ( '' )
const [ newDescription , setNewDescription ] = useState ( '' )
const [ busy , setBusy ] = useState ( false )
const [ roleLoading , setRoleLoading ] = useState ( false )
const [ notice , setNotice ] = useState ( '' )
const [ err , setErr ] = useState ( '' )
const requestSequence = useRef ( 0 )
const selectedIdRef = useRef ( '' )
selectedIdRef . current = selectedId
const selected = roles ? . find ( role = > role . roleId === selectedId ) ? ? null
const groups = useMemo ( ( ) = > [ . . . new Set ( catalog . map ( item = > item . group ) ) ] , [ catalog ] )
const filtered = useMemo ( ( ) = > filterRoles ( roles ? ? [ ] , query , mode , status ) , [ roles , query , mode , status ] )
const filtered = roles ? ? [ ]
const pageItems = useMemo ( ( ) = > groupPermissionPage ( catalog , group , page , 6 ) , [ catalog , group , page ] )
const editablePageItems = pageItems . filter ( item = > ! item . protected )
const changes = draft ? visiblePatch ( draft , catalog . filter ( item = > ! item . protected ) . map ( item = > item . actionId ) ) : [ ]
const load = async ( ) = > {
const load = async ( pageCursor = roleCursor , refreshCatalog = false , preferredId = selectedId ,
filters = { query , mode , status , action : actionFilter } ) = > {
const sequence = ++ requestSequence . current
setRoleLoading ( true )
setErr ( '' )
try {
const [ rolePage , descriptors ] = await Promise . all ( [
api . listRoles ( { limit : 100 } ) , api . permissionCatalog ( ) ,
] )
if ( ! shouldAcceptRoleResponse ( requestSequence . current , sequence ) ) return
setRoles ( rolePage . roles ? ? [ ] )
setCatalog ( descriptors )
const nextSelected = rolePage . roles . find ( role = > role . roleId === selectedId ) ? ? rolePage . roles [ 0 ] ? ? null
if ( nextSelected ) selectRole ( nextSelected , descriptors , sequence )
else { setSelectedId ( '' ) ; setDraft ( null ) }
} catch ( error ) {
if ( shouldAcceptRoleResponse ( requestSequence . current , sequence ) ) setErr ( String ( error ) )
const [ roleResult , catalogResult ] = await Promise . allSettled ( [
api . listRoles ( { query : filters.query.trim ( ) , mode : filters.mode , status : filters.status ,
action : filters.action || undefined , cursor : pageCursor , limit : 50 } ) ,
canViewPermissions && ( refreshCatalog || catalog . length === 0 ) ? api . permissionCatalog ( ) : Promise . resolve ( catalog ) ,
] )
if ( ! shouldAcceptRoleResponse ( requestSequence . current , sequence ) ) return
setRoleLoading ( false )
const descriptors = catalogResult . status === 'fulfilled' ? catalogResult.value : catalog
if ( catalogResult . status === 'fulfilled' ) setCatalog ( descriptors )
if ( roleResult . status === 'rejected' ) {
setErr ( String ( roleResult . reason ) )
return
}
if ( catalogResult . status === 'rejected' ) setErr ( ` 权限目录加载失败: ${ catalogResult . reason } ` )
const rolePage = roleResult . value
setRoles ( rolePage . roles ? ? [ ] )
setNextRoleCursor ( rolePage . nextCursor ? ? '' )
const nextSelected = rolePage . roles . find ( role = > role . roleId === preferredId ) ? ? rolePage . roles [ 0 ] ? ? null
if ( nextSelected ) selectRole ( nextSelected , descriptors , sequence )
else { setSelectedId ( '' ) ; setDraft ( null ) }
}
const selectRole = ( role : RoleRecord , descriptors = catalog , sequence? : number ) = > {
@@ -101,48 +123,99 @@ export function RolePermissionsPanel() {
setErr ( '' )
}
useEffect ( ( ) = > { void load ( ) ; return ( ) = > { requestSequence . current ++ } } , [ ] )
useEffect ( ( ) = > {
setNextRoleCursor ( '' ) ; setRoleLoading ( true )
const timer = window . setTimeout ( ( ) = > { void load ( roleCursor ) } , 180 )
return ( ) = > { window . clearTimeout ( timer ) ; requestSequence . current ++ }
} , [ query , mode , status , actionFilter , roleCursor , canViewPermissions ] )
const create = async ( ) = > {
if ( ! newName . trim ( ) ) return
requestSequence . current ++ ; setRoleLoading ( false )
setBusy ( true ) ; setErr ( '' ) ; setNotice ( '' )
try {
const role = await api . createRole ( newName . trim ( ) , newDescription . trim ( ) )
setNewName ( '' ) ; setNewDescription ( '' )
await load ( )
setQuery ( '' ) ; setStatus ( 'all' ) ; setActionFilter ( '' ) ; setRoleCursor ( '' ) ; setRoleTrail ( [ ] )
setRoles ( current = > [ role , . . . ( current ? ? [ ] ) . filter ( item = > item . roleId !== role . roleId ) ] )
selectRole ( role , catalog , ++ requestSequence . current )
setNotice ( ` 已创建角色 ${ role . name } ` )
await load ( '' , true , role . roleId , { query : '' , mode , status : 'all' , action : '' } )
setNotice ( ` 已创建角色 ${ role . name } ` )
} catch ( error ) { setErr ( String ( error ) ) } finally { setBusy ( false ) }
}
const saveMetadata = async ( ) = > {
if ( ! selected ) return
const roleId = selected . roleId
const operationSequence = ++ requestSequence . current
setRoleLoading ( false )
setBusy ( true ) ; setErr ( '' ) ; setNotice ( '' )
try {
await api . patchRole ( selected . roleId , selected . roleVersion , { name : name.trim ( ) , description : description.trim ( ) } )
await load ( ) ; setNotice ( '角色资料已保存' )
} catch ( error ) { setErr ( String ( error ) . includes ( '409' ) ? '角色已被其他操作修改,请刷新后重试' : String ( error ) ) }
} catch ( error ) {
if ( String ( error ) . includes ( '409' ) ) {
try {
const fresh = await api . getRole ( roleId )
if ( requestSequence . current !== operationSequence || selectedIdRef . current !== roleId ) return
setRoles ( current = > current ? . some ( role = > role . roleId === fresh . roleId )
? current . map ( role = > role . roleId === fresh . roleId ? fresh : role ) : [ fresh , . . . ( current ? ? [ ] ) ] )
setErr ( '角色资料版本冲突;已读取最新版本并保留本地名称与描述,请核对后重试' )
} catch ( refreshError ) { setErr ( String ( refreshError ) ) }
} else setErr ( String ( error ) )
}
finally { setBusy ( false ) }
}
const setEnabled = async ( ) = > {
if ( ! selected ) return
const roleId = selected . roleId
const operationSequence = ++ requestSequence . current
setRoleLoading ( false )
setBusy ( true ) ; setErr ( '' ) ; setNotice ( '' )
try {
await api . patchRole ( selected . roleId , selected . roleVersion , { enabled : ! selected . enabled } )
await load ( ) ; setNotice ( selected . enabled ? '角色已停用,现有绑定不再授予权限' : '角色已启用' )
} catch ( error ) { setErr ( String ( error ) ) } finally { setBusy ( false ) }
} catch ( error ) {
if ( String ( error ) . includes ( '409' ) ) {
try {
const fresh = await api . getRole ( roleId )
if ( requestSequence . current !== operationSequence || selectedIdRef . current !== roleId ) return
setRoles ( current = > current ? . some ( role = > role . roleId === fresh . roleId )
? current . map ( role = > role . roleId === fresh . roleId ? fresh : role ) : [ fresh , . . . ( current ? ? [ ] ) ] )
setErr ( '角色状态版本冲突;已刷新当前状态,请重新确认' )
} catch ( refreshError ) { setErr ( String ( refreshError ) ) }
} else setErr ( String ( error ) )
} finally { setBusy ( false ) }
}
const rebasePermissionConflict = async ( roleId : string , currentDraft : RolePermissionDraft , operationSequence : number ) = > {
const fresh = await api . getRole ( roleId )
if ( requestSequence . current !== operationSequence || selectedIdRef . current !== roleId ) return
setRoles ( current = > current ? . some ( role = > role . roleId === roleId )
? current . map ( role = > role . roleId === roleId ? fresh : role ) : [ fresh , . . . ( current ? ? [ ] ) ] )
setDraft ( rebaseRoleDraft ( currentDraft , roleValues ( fresh , catalog ) ) )
setErr ( '权限版本冲突;已基于最新版本重放本地草稿,请核对后再次保存' )
}
const savePermissions = async ( ) = > {
if ( ! selected || ! draft ) return
const roleId = selected . roleId
const operationSequence = ++ requestSequence . current
setRoleLoading ( false )
const invalid = validateRolePermissionDraft ( draft )
if ( invalid ) { setErr ( ` ${ invalid . actionId } 的默认权限不能超过角色上限 ` ) ; return }
const full = Object . entries ( draft . values ) . map ( ( [ actionId , value ] ) = > ( { actionId , . . . value } ) )
setBusy ( true ) ; setErr ( '' ) ; setNotice ( '' )
try {
await api . replaceRolePermissions ( selected . roleId , selected . roleVersion , full , crypto . randomUUID ( ) )
await load ( ) ; setNotice ( '角色权限已保存' )
} catch ( error ) { setErr ( String ( error ) . includes ( '409' ) ? '权限版本冲突,草稿已保留,请核对后重试' : String ( error ) ) }
await load ( roleCursor , true , selected . roleId ) ; setNotice ( '角色权限已保存' )
} catch ( error ) {
if ( String ( error ) . includes ( '409' ) ) {
try { await rebasePermissionConflict ( roleId , draft , operationSequence ) } catch ( refreshError ) { setErr ( String ( refreshError ) ) }
} else setErr ( String ( error ) )
}
finally { setBusy ( false ) }
}
@@ -155,18 +228,33 @@ export function RolePermissionsPanel() {
setDraft ( updateRolePermission ( draft , actionId , value ) )
}
const editVisible = ( field : 'allowed' | 'capAllowed' , checked : boolean ) = > {
if ( ! draft ) return
let next = draft
for ( const item of editablePageItems ) {
const current = next . values [ item . actionId ] ? ? { allowed : false , capAllowed : false }
const value = field === 'allowed'
? { allowed : checked , capAllowed : checked ? true : current . capAllowed }
: { allowed : checked ? current.allowed : false , capAllowed : checked }
next = updateRolePermission ( next , item . actionId , value )
}
setDraft ( next )
}
const matrix = ( ) = > < div className = "rp-matrix-wrap" >
< table className = "ad-table rp-matrix" >
< thead > < tr > < th > 角 色 < / th > { pageItems . map ( item = > < th key = { item . actionId } > { item . label } < / th > ) } < / tr > < / thead >
< tbody > { filtered . map ( role = > < tr key = { role . roleId } className = { role . roleId === selectedId ? 'rp-selected' : '' } >
< td > < button className = "ad-mini" onClick = { ( ) = > selectRole ( role ) } > { role . name } < / button > < span className = "ad-dim" > · { role . bindingCount } 人 < / span > < / td >
< td > < button className = "ad-mini" disabled = { busy || roleLoading } onClick = { ( ) = > selectRole ( role ) } > { role . name } < / button > < span className = "ad-dim" > · { role . bindingCount } 人 < / span > < / td >
{ pageItems . map ( item = > {
const value = role . roleId === selectedId && draft ? draft . values [ item . actionId ] : roleValue ( role , item . actionId )
return < td key = { item . actionId } >
{ item . protected ? < span className = "ad-dim" > 系 统 授 权 < / span > : < label className = "ad-check" >
< input type = "checkbox" aria-label = { ` ${ role . name } ${ item . label } ` } checked = { value ? . allowed ? ? false }
disabled = { busy || role . roleId !== selectedId } onChange = { event = > editValue ( item . actionId , 'allowed' , event . target . checked ) } / > 允 许
< / label > }
{ item . protected ? < span className = "ad-dim" > 系 统 授 权 < / span > : < >
< label className = "ad-check" > < input type = "checkbox" aria-label = { ` ${ role . name } ${ item . label } 默认允许 ` } checked = { value ? . allowed ? ? false }
disabled = { busy || roleLoading || ! canManagePermissions || role . roleId !== selectedId } onChange = { event = > editValue ( item . actionId , 'allowed' , event . target . checked ) } / > 默 认 < / label >
< label className = "ad-check" > < input type = "checkbox" aria-label = { ` ${ role . name } ${ item . label } 角色上限 ` } checked = { value ? . capAllowed ? ? false }
disabled = { busy || roleLoading || ! canManagePermissions || role . roleId !== selectedId } onChange = { event = > editValue ( item . actionId , 'capAllowed' , event . target . checked ) } / > 上 限 < / label >
< / > }
< / td >
} ) }
< / tr > ) } < / tbody >
@@ -174,56 +262,68 @@ export function RolePermissionsPanel() {
< / div >
return < div className = "rp-panel" >
< div className = "ad-create" >
{ canManageRoles && < div className = "ad-create" >
< div className = "ad-row" >
< input className = "lg-input" aria-label = "新角色名称" placeholder = "角色名称" value = { newName } onChange = { event = > setNewName ( event . target . value ) } / >
< input className = "lg-input" aria-label = "新角色描述" placeholder = "描述(可空)" value = { newDescription } onChange = { event = > setNewDescription ( event . target . value ) } / >
< button className = "lg-submit ad-create-btn" disabled = { busy || ! newName . trim ( ) } onClick = { create } > 创 建 角 色 < / button >
< button className = "lg-submit ad-create-btn" disabled = { busy || roleLoading || ! newName . trim ( ) } onClick = { create } > 创 建 角 色 < / button >
< / div >
< / div >
< / div > }
< Collapsible id = "role-permission-filters" trigger = "搜索与过滤" >
< div className = "ad-row rp-filters" >
< input className = "lg-input" aria-label = "搜索角色" placeholder = "角色名称、描述或 ID" value = { query } onChange = { event = > setQuery ( event . target . value ) } / >
< select className = "lg-input" aria-label = "角色匹配方式" value = { mode } onChange = { event = > setMode ( event . target . value as typeof mode ) } >
< input className = "lg-input" aria-label = "搜索角色" placeholder = "角色名称、描述或 ID" value = { query } disabled = { busy } onChange = { event = > { setQuery ( event . target . value ) ; setRoleCursor ( '' ) ; setRoleTrail ( [ ] ) ; setNextRoleCursor ( '' ) ; setRoleLoading ( true ) } } / >
< select className = "lg-input" aria-label = "角色匹配方式" value = { mode } disabled = { busy } onChange = { event = > { setMode ( event . target . value as typeof mode ) ; setRoleCursor ( '' ) ; setRoleTrail ( [ ] ) ; setNextRoleCursor ( '' ) ; setRoleLoading ( true ) } } >
< option value = "fuzzy" > 模 糊 匹 配 < / option > < option value = "exact" > 精 确 匹 配 < / option >
< / select >
< select className = "lg-input" aria-label = "角色状态" value = { status } onChange = { event = > setStatus ( event . target . value as typeof status ) } >
< select className = "lg-input" aria-label = "角色状态" value = { status } disabled = { busy } onChange = { event = > { setStatus ( event . target . value as typeof status ) ; setRoleCursor ( '' ) ; setRoleTrail ( [ ] ) ; setNextRoleCursor ( '' ) ; setRoleLoading ( true ) } } >
< option value = "all" > 全 部 状 态 < / option > < option value = "enabled" > 启 用 < / option > < option value = "disabled" > 停 用 < / option >
< / select >
< select className = "lg-input" aria-label = "权限分组" value = { group } onChange = { event = > { setGroup ( event . target . value ) ; setPage ( 0 ) } } >
< option value = "" > 全 部 权 限 < / option > { groups . map ( value = > < option key = { value } > { value } < / option > ) }
< / select >
{ canViewPermissions && < select className = "lg-input" aria-label = "按权限过滤角色" value = { actionFilter } disabled = { busy }
onChange = { event = > { setActionFilter ( event . target . value ) ; setRoleCursor ( '' ) ; setRoleTrail ( [ ] ) ; setNextRoleCursor ( '' ) ; setRoleLoading ( true ) } } >
< option value = "" > 全 部 角 色 权 限 < / option > { catalog . filter ( item = > ! item . protected ) . map ( item = > < option key = { item . actionId } value = { item . actionId } > { item . label } < / option > ) }
< / select > }
< / div >
< / Collapsible >
< div className = "ad-row rp-view-toggle" >
< button className = { ` ad-mini ${ view === 'single' ? 'on' : '' } ` } onClick = { ( ) = > setView ( 'single' ) } > 单 角 色 配 置 < / button >
< button className = { ` ad-mini ${ view === 'matrix' ? 'on' : '' } ` } onClick = { ( ) = > setView ( 'matrix' ) } > 角 色 矩 阵 < / button >
{ canViewPermissions && < button className = { ` ad-mini ${ view === 'matrix' ? 'on' : '' } ` } onClick = { ( ) = > setView ( 'matrix' ) } > 角 色 矩 阵 < / button > }
< button className = "ad-mini" disabled = { page === 0 } onClick = { ( ) = > setPage ( value = > value - 1 ) } > 上 一 组 < / button >
< button className = "ad-mini" disabled = { ( page + 1 ) * 6 >= catalog . filter ( item = > ! group || item . group === group ) . length } onClick = { ( ) = > setPage ( value = > value + 1 ) } > 下 一 组 < / button >
< button className = "ad-mini" disabled = { busy || roleLoading || ! roleTrail . length } onClick = { ( ) = > { setRoleCursor ( roleTrail [ roleTrail . length - 1 ] ) ; setRoleTrail ( roleTrail . slice ( 0 , - 1 ) ) } } > 上 一 页 角 色 < / button >
< button className = "ad-mini" disabled = { busy || roleLoading || ! nextRoleCursor } onClick = { ( ) = > { setRoleTrail ( [ . . . roleTrail , roleCursor ] ) ; setRoleCursor ( nextRoleCursor ) } } > 下 一 页 角 色 < / button >
{ view === 'matrix' && canManagePermissions && < >
< button className = "ad-mini" disabled = { busy || roleLoading || ! editablePageItems . length } onClick = { ( ) = > editVisible ( 'allowed' , true ) } > 当 前 页 默 认 全 选 < / button >
< button className = "ad-mini" disabled = { busy || roleLoading || ! editablePageItems . length } onClick = { ( ) = > editVisible ( 'allowed' , false ) } > 当 前 页 默 认 清 除 < / button >
< button className = "ad-mini" disabled = { busy || roleLoading || ! editablePageItems . length } onClick = { ( ) = > editVisible ( 'capAllowed' , true ) } > 当 前 页 上 限 全 开 < / button >
< button className = "ad-mini" disabled = { busy || roleLoading || ! editablePageItems . length } onClick = { ( ) = > editVisible ( 'capAllowed' , false ) } > 当 前 页 上 限 关 闭 < / button >
< / > }
< / div >
{ notice && < div className = "ad-notice" > < Icon name = "check" size = { 14 } / > { notice } < / div > }
{ err && < div className = "lg-err" role = "alert" > < Icon name = "alert" size = { 14 } / > { err } < / div > }
{ view === 'matrix' ? matrix ( ) : < div className = "rp-single" >
< aside className = "rp-role-list" >
{ ( filtered ) . map ( role = > < button key = { role . roleId } className = { ` az-nav-item ${ role . roleId === selectedId ? 'on' : '' } ` } onClick = { ( ) = > selectRole ( role ) } >
{ ( filtered ) . map ( role = > < button key = { role . roleId } disabled = { busy || roleLoading } className = { ` az-nav-item ${ role . roleId === selectedId ? 'on' : '' } ` } onClick = { ( ) = > selectRole ( role ) } >
< span > { role . name } < / span > < span className = "ad-dim" > { role . bindingCount } 人 < / span >
< / button > ) }
< / aside >
< section className = "rp-editor" >
{ selected && draft ? < >
< div className = "ad-row" >
< input className = "lg-input" aria-label = "角色名称" value = { name } onChange = { event = > setName ( event . target . value ) } / >
< input className = "lg-input" aria-label = "角色描述" value = { description } onChange = { event = > setDescription ( event . target . value ) } / >
< button className = "ad-mini" disabled = { busy } onClick = { saveMetadata } > 保 存 资 料 < / button >
< button className = "ad-mini" disabled = { busy } onClick = { setEnabled } > { selected . enabled ? '停用角色' : '启用角色' } < / button >
< input className = "lg-input" aria-label = "角色名称" value = { name } disabled = { ! canManageRoles || busy || roleLoading } onChange = { event = > setName ( event . target . value ) } / >
< input className = "lg-input" aria-label = "角色描述" value = { description } disabled = { ! canManageRoles || busy || roleLoading } onChange = { event = > setDescription ( event . target . value ) } / >
{ canManageRoles && < button className = "ad-mini" disabled = { busy || roleLoading } onClick = { saveMetadata } > 保 存 资 料 < / button > }
{ canManageRoles && < button className = "ad-mini" disabled = { busy || roleLoading } onClick = { setEnabled } > { selected . enabled ? '停用角色' : '启用角色' } < / button > }
< / div >
< table className = "ad-table" > < thead > < tr > < th > 权 限 < / th > < th > 默 认 允 许 < / th > < th > 角 色 上 限 < / th > < th > 说 明 < / th > < / tr > < / thead >
{ canViewPermissions ? < > < table className = "ad-table" > < thead > < tr > < th > 权 限 < / th > < th > 默 认 允 许 < / th > < th > 角 色 上 限 < / th > < th > 说 明 < / th > < / tr > < / thead >
< tbody > { pageItems . map ( item = > {
const value = draft . values [ item . actionId ] ? ? { allowed : false , capAllowed : false }
return < tr key = { item . actionId } > < td > { item . label } < div className = "ad-dim ad-mono" > { item . actionId } < / div > < / td >
< td > < input type = "checkbox" aria-label = { ` ${ item . label } 默认允许 ` } checked = { value . allowed } disabled = { busy || item . protected }
< td > < input type = "checkbox" aria-label = { ` ${ item . label } 默认允许 ` } checked = { value . allowed } disabled = { busy || roleLoading || ! canManagePermissions || item . protected }
onChange = { event = > editValue ( item . actionId , 'allowed' , event . target . checked ) } / > < / td >
< td > < input type = "checkbox" aria-label = { ` ${ item . label } 角色上限 ` } checked = { value . capAllowed } disabled = { busy || item . protected }
< td > < input type = "checkbox" aria-label = { ` ${ item . label } 角色上限 ` } checked = { value . capAllowed } disabled = { busy || roleLoading || ! canManagePermissions || item . protected }
onChange = { event = > editValue ( item . actionId , 'capAllowed' , event . target . checked ) } / > < / td >
< td className = "ad-dim" > { item . protected ? '受保护系统授权,不能由普通角色授予' : item . description } < / td > < / tr >
} ) } < / tbody > < / table >
@@ -231,7 +331,8 @@ export function RolePermissionsPanel() {
{ changes . length === 0 ? < p className = "ad-hint" > 没 有 权 限 变 化 < / p > : < ul className = "rp-diff" > { changes . map ( change = >
< li key = { change . actionId } > < span className = "ad-mono" > { change . actionId } < / span > : 默 认 { change . allowed ? '允许' : '拒绝' } , 上 限 { change . capAllowed ? '允许' : '拒绝' } < / li > ) } < / ul > }
< / Collapsible >
< button className = "lg-submit ad-create-btn" disabled = { busy || changes . length === 0 || editablePageItems . length === 0 } onClick = { savePermissions } > 保 存 权 限 < / button >
{ canManagePermissions && < button className = "lg-submit ad-create-btn" disabled = { busy || roleLoading || changes . length === 0 || editablePageItems . length === 0 } onClick = { savePermissions } > 保 存 权 限 < / button > }
< / > : < p className = "ad-hint" > 当 前 账 号 没 有 查 看 角 色 权 限 目 录 的 系 统 权 限 < / p > }
< / > : < p className = "ad-hint" > 请 选 择 角 色 < / p > }
< / section >
< / div > }