@@ -40,7 +40,7 @@ const filters = ref<PermissionFilters>({ keyword: '', type: '', status: '' })
const appliedFilters = ref < PermissionFilters > ( { ... filters . value } )
const queryFields : QueryField [ ] = [
{ key : 'keyword' , type : 'input' , label : '权限关键词' , placeholder : '权限名称、编码或路径' } ,
{ key : 'type' , type : 'select' , label : '类型' , placeholder : '全部类型' , options : [ { label : '菜单' , value : '菜单' } , { label : '按钮' , value : '按钮 ' } ] } ,
{ key : 'type' , type : 'select' , label : '类型' , placeholder : '全部类型' , options : [ { label : '菜单' , value : '菜单' } , { label : '页面' , value : '页面' } , { label : '按钮' , value : '按钮' } , { label : '接口' , value : '接口 ' } ] } ,
{ key : 'status' , type : 'select' , label : '状态' , placeholder : '全部状态' , options : [ { label : '启用' , value : '启用' } , { label : '停用' , value : '停用' } ] } ,
]
const expandedRowKeys = ref < string [ ] > ( [ ] )
@@ -51,8 +51,9 @@ const formRules = computed<FormRules>(() => ({
name : [ { required : true , whitespace : true , message : '请输入权限名称' , trigger : 'blur' } ] ,
code : [ { required : true , whitespace : true , message : '请输入权限编码' , trigger : 'blur' } ] ,
type : [ { required : true , message : '请选择资源类型' , trigger : 'change' } ] ,
parentId : form . type === '按钮' ? [ { required : true , message : '请选择上级菜单' , trigger : 'change' } ] : [ ] ,
path : form . type === '菜单' ? [ { required : true , whitespace : true , message : '请输入菜单路由' , trigger : 'blur' } ] : [ ] ,
parentId : [ '按钮' , '页面' , '接口' ] . includes ( form . type ) ? [ { required : true , message : '请选择上级菜单' , trigger : 'change' } ] : [ ] ,
path : [ '菜单' , '页面' ] . includes ( form . type ) ? [ { required : true , whitespace : true , message : '请输入菜单路由' , trigger : 'blur' } ] : [ ] ,
method : form . type === '接口' ? [ { required : true , message : '请选择 HTTP 方法' , trigger : 'change' } ] : [ ] ,
sort : [ { required : true , type : 'number' , message : '请输入显示排序' , trigger : 'change' } ] ,
} ) )
const editorLabel = computed ( ( ) => form . id ? '编辑权限' : '新增权限' )
@@ -64,7 +65,7 @@ const { captureBaseline } = useUnsavedWorkRegistration({
model : form ,
requiredPermission ,
} )
const availablePermissionTypes : PermissionType [ ] = [ '菜单' , '按钮 ' ]
const availablePermissionTypes : PermissionType [ ] = [ '菜单' , '页面' , '按钮' , '接口 ']
const permissions = computed ( ( ) => serverPermissions . value )
const canCreate = computed ( ( ) => access . can ( 'platform:permission:create' ) )
const canEdit = computed ( ( ) => access . can ( 'platform:permission:edit' ) )
@@ -82,7 +83,7 @@ const blockedParentIds = computed(() => new Set(
form . id ? [ form . id , ... getDescendantIds ( permissions . value , form . id ) ] : [ ] ,
) )
const parentOptions = computed ( ( ) => permissions . value . filter ( ( item ) => (
item . type === '菜单' && ! blockedParentIds . value . has ( item . id )
[ '目录' , '菜单' , '页面' ] . includes ( item . type ) && ! blockedParentIds . value . has ( item . id )
) ) )
const permissionColumns : DataTableColumn [ ] = [
{ key : 'actions' , label : '操作' , width : 280 , fixed : 'left' , slotName : 'actions' } ,
@@ -127,7 +128,7 @@ function permissionSummary(row: PermissionTreeNode) {
const descendants = collectDescendantIds ( row . children ? ? [ ] )
. map ( ( id ) => permissions . value . find ( ( item ) => item . id === id ) )
. filter ( ( item ) : item is Permission => Boolean ( item ) )
const menuCount = descendants . filter ( ( item ) => item . type === '菜单' ) . length
const menuCount = descendants . filter ( ( item ) => [ '菜单' , '页面' ] . includes ( item . type ) ) . length
const actionCount = descendants . filter ( ( item ) => [ '按钮' , '接口' ] . includes ( item . type ) ) . length
if ( menuCount === 0 && actionCount === 0 ) return '无下级权限'
return [ menuCount ? ` ${ menuCount } 个菜单 ` : '' , actionCount ? ` ${ actionCount } 项操作 ` : '' ] . filter ( Boolean ) . join ( ' · ' )
@@ -157,8 +158,9 @@ function openEdit(row: Permission) {
nextTick ( ( ) => formRef . value ? . clearValidate ( ) )
}
function handleTypeChange ( ) {
if ( form . type === '按钮' ) form . path = ''
nextTick ( ( ) => formRef . value ? . clearValidate ( [ 'parentId' , 'path' ] ) )
if ( ! [ '菜单' , '页面' ] . includes ( form . type ) ) form . path = ''
form . method = form . type === '接口' ? ( form . method === '-' ? 'GET' : form . method ) : '-'
nextTick ( ( ) => formRef . value ? . clearValidate ( [ 'parentId' , 'path' , 'method' ] ) )
}
async function savePermission ( ) {
const valid = await formRef . value ? . validate ( ) . catch ( ( ) => false )
@@ -170,7 +172,7 @@ async function savePermission() {
const updated = await updatePlatformPermission ( form . id , {
name : form . name . trim ( ) ,
parentId : form . parentId ,
routePath : form . type === '菜单' ? form . path . trim ( ) : null ,
routePath : [ '菜单' , '页面' ] . includes ( form . type ) ? form . path . trim ( ) : null ,
httpMethod : form . method === '-' ? null : form . method ,
sortNo : form . sort ,
} )
@@ -179,12 +181,12 @@ async function savePermission() {
ElMessage . success ( '权限资源已更新' )
} else {
const created = await createPlatformPermission ( {
type : form . type === '按钮' ? 'BUTTON' : 'MENU' ,
type : ( { 目录 : 'MENU' , 菜单 : 'MENU' , 页面 : 'PAGE' , 按钮 : 'BUTTON' , 接口 : 'API' } as const ) [ form . type ] ,
code : form . code . trim ( ) ,
name : form . name . trim ( ) ,
parentId : form . parentId || '0' ,
routePath : form . type === '菜单' ? form . path . trim ( ) : null ,
httpMethod : null ,
routePath : [ '菜单' , '页面' ] . includes ( form . type ) ? form . path . trim ( ) : null ,
httpMethod : form . type === '接口' ? form . method : null ,
sortNo : form . sort ,
} )
serverPermissions . value . push ( mapPlatformPermission ( created ) )
@@ -229,10 +231,10 @@ async function removePermission(row: Permission) {
rowActionId . value = ''
}
}
function typeClass ( type : PermissionType ) { return { '目录' : 'directory' , '菜单' : 'menu' , '按钮' : 'action' , '接口' : 'api' } [ type ] }
function typeClass ( type : PermissionType ) { return { '目录' : 'directory' , '菜单' : 'menu' , '页面' : 'menu' , '按钮' : 'action' , '接口' : 'api' } [ type ] }
function canAddChild ( row : Permission ) {
return canCreate . value && row . type === '菜单'
return canCreate . value && [ '菜单' , '页面' ] . includes ( row . type )
}
function canEditRow ( row : Permission ) {
return canEdit . value && ! row . builtIn
@@ -292,7 +294,7 @@ onMounted(loadPermissions)
< / div >
< span v-else class = "secondary-text" > 仅查看 < / span >
< / template >
< template # name = "{ row }" > < div class = "permission-name" > < span : class = "`is-${typeClass((row as Permission).type)}`" > < FolderTree v-if = "(row as Permission).type === '目录' || (row as Permission).type === '菜单' " :size="16" /><ShieldCheck v-else-if="(row as Permission).type === '按钮'" :size="16" /><Braces v-else :size="16" /></span><div><strong>{{ (row as Permission).name }}</strong><small v-if="(row as PermissionTreeNode).children?.length" > {{ permissionSummary ( row as PermissionTreeNode ) }} < / small > < / div > < / div > < / template >
< template # name = "{ row }" > < div class = "permission-name" > < span : class = "`is-${typeClass((row as Permission).type)}`" > < FolderTree v-if = "['目录', '菜单', '页面'].includes( (row as Permission).type) " :size="16" /><ShieldCheck v-else-if="(row as Permission).type === '按钮'" :size="16" /><Braces v-else :size="16" /></span><div><strong>{{ (row as Permission).name }}</strong><small v-if="(row as PermissionTreeNode).children?.length" > {{ permissionSummary ( row as PermissionTreeNode ) }} < / small > < / div > < / div > < / template >
< template # code = "{ row }" > < code class = "code-value" > { { ( row as Permission ) . code } } < / code > < / template >
< template # endpoint = "{ row }" > < div class = "endpoint-cell" > < span v-if = "(row as Permission).method !== '-'" > {{ ( row as Permission ) .method }} < / span > < code > { { ( row as Permission ) . path || '-' } } < / code > < / div > < / template >
< template # status = "{ row }" > < StatusBadge : status = "(row as Permission).status" / > < / template >
@@ -308,12 +310,13 @@ onMounted(loadPermissions)
< el-form-item label = "权限名称" prop = "name" required > < el-input v-model = "form.name" placeholder="用户可理解的中文名称" maxlength="64" / > < / el-form-item >
< el-form-item label = "权限编码" prop = "code" required > < el-input v-model = "form.code" :disabled="Boolean(form.id)" placeholder="如:platform:customer:list" maxlength="128" / > < / el-form-item >
< el-form-item label = "资源类型" prop = "type" required > < el-select v-model = "form.type" :disabled="Boolean(form.id)" @change="handleTypeChange"><el-option v-for="item in availablePermissionTypes" :key="item" :label="item" :value="item" / > < / el-select > < / el-form-item >
< el-form-item label = "上级资源" prop = "parentId" : required = "form.type === '按钮' " > < el-select v-model = "form.parentId" clearable placeholder="作为一级资源"><el-option v-for="item in parentOptions" :key="item.id" :label="`${item.name} (${item.code})`" :value="item.id" / > < / el-select > < / el-form-item >
< el-form-item v-if = "form.type === '菜单' " label="路由路径" prop="path" required><el-input v-model="form.path" placeholder="如:/customers" / > < / el-form-item >
< el-form-item label = "上级资源" prop = "parentId" : required = "['按钮', '页面', '接口'].includes(form.type) " > < el-select v-model = "form.parentId" clearable placeholder="作为一级资源"><el-option v-for="item in parentOptions" :key="item.id" :label="`${item.name} (${item.code})`" :value="item.id" / > < / el-select > < / el-form-item >
< el-form-item v-if = "['菜单', '页面'].includes(form.type) " label="路由路径" prop="path" required><el-input v-model="form.path" placeholder="如:/customers" / > < / el-form-item >
< el-form-item v-if = "form.type === '接口'" label="HTTP 方法" prop="method" required><el-select v-model="form.method"><el-option v-for="method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']" :key="method" :label="method" :value="method" / > < / el-select > < / el-form-item >
< el-form-item label = "显示排序" prop = "sort" required > < el-input-number v-model = "form.sort" :min="0" :max="999" controls-position="right" / > < / el-form-item >
< / div >
< / FormSection >
< div class = "form-callout" > < Braces :size = "18" / > < div > < strong > 权限编码约定 < / strong > < p > 使用稳定的 “ 业务域 : 资源 : 动作 ” 编码 ; 一期真实资源只创建菜单和按钮 , 接口鉴权由服务端实现 。 < / p > < / div > < / div >
< div class = "form-callout" > < Braces :size = "18" / > < div > < strong > 权限编码约定 < / strong > < p > 使用稳定的 “ 业务域 : 资源 : 动作 ” 编码 ; 资源类型与后端 MENU 、 PAGE 、 BUTTON 、 API 契约一致 。 < / p > < / div > < / div >
< / el-form >
< / AppDrawer >
< / AdminPage >