2026-07-30 17:59:43 +08:00
< script setup lang = "ts" >
2026-08-01 12:17:33 +08:00
import { computed , nextTick , onMounted , reactive , ref } from 'vue'
import { ElMessage , ElMessageBox , type FormInstance , type FormRules } from 'element-plus'
2026-08-10 17:42:17 +08:00
import { Braces , FolderTree , MoreHorizontal , Pencil , Plus , Power , ShieldCheck , Trash2 } from '@lucide/vue'
2026-08-03 18:49:18 +08:00
import StatusBadge from '@/shared/components/StatusBadge.vue'
import DataTable , { type DataTableColumn } from '@/shared/components/DataTable.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
2026-08-10 15:36:53 +08:00
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import FormSection from '@/shared/components/FormSection.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
2026-08-01 12:17:33 +08:00
import {
changePlatformPermissionStatus ,
createPlatformPermission ,
deletePlatformPermission ,
listPlatformPermissions ,
updatePlatformPermission ,
2026-08-03 18:49:18 +08:00
} from '@/modules/iam/api/platformIam'
import { getApiErrorDetails , getApiErrorMessage , type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import type { Permission , PermissionType } from '@/modules/iam/types'
import { buildPermissionTree , getDescendantIds , type PermissionTreeNode } from '@/modules/iam/utils/permissionTree'
import { mapPlatformPermission } from '@/modules/iam/utils/platformPermission'
2026-07-30 17:59:43 +08:00
const access = useAccessPreviewStore ()
const editorOpen = ref ( false )
2026-08-01 12:17:33 +08:00
const formRef = ref < FormInstance >()
2026-07-30 17:59:43 +08:00
const serverPermissions = ref < Permission [] >([])
const loading = ref ( false )
const saving = ref ( false )
2026-08-01 12:17:33 +08:00
const rowActionId = ref ( '' )
2026-07-30 17:59:43 +08:00
const loadError = ref < ApiErrorDetails | null >( null )
2026-08-01 12:17:33 +08:00
const page = ref ( 1 )
const pageSize = ref ( 10 )
type PermissionFilters = QueryModel & { keyword : string ; type : string ; status : string }
const filters = ref < PermissionFilters >({ keyword : '' , type : '' , status : '' })
const appliedFilters = ref < PermissionFilters >({ ... filters . value })
const queryFields : QueryField [] = [
2026-08-06 19:03:13 +08:00
{ key : 'keyword' , type : 'input' , label : '权限关键词' , placeholder : '权限名称、编码或路径' },
2026-08-13 11:57:44 +08:00
{ key : 'type' , type : 'select' , label : '类型' , placeholder : '全部类型' , options : [{ label : '菜单' , value : '菜单' }, { label : '页面' , value : '页面' }, { label : '按钮' , value : '按钮' }, { label : '接口' , value : '接口' }] },
2026-08-06 19:03:13 +08:00
{ key : 'status' , type : 'select' , label : '状态' , placeholder : '全部状态' , options : [{ label : '启用' , value : '启用' }, { label : '停用' , value : '停用' }] },
2026-08-01 12:17:33 +08:00
]
const expandedRowKeys = ref < string [] >([])
const tableRenderKey = ref ( 0 )
2026-07-30 17:59:43 +08:00
const emptyForm = () : Permission => ({ id : '' , parentId : null , name : '' , code : '' , type : '菜单' , path : '' , method : '-' , status : '启用' , sort : 10 })
const form = reactive ( emptyForm ())
2026-08-01 12:17:33 +08:00
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' }],
2026-08-13 11:57:44 +08:00
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' }] : [],
2026-08-01 12:17:33 +08:00
sort : [{ required : true , type : 'number' , message : '请输入显示排序' , trigger : 'change' }],
}))
2026-07-30 17:59:43 +08:00
const editorLabel = computed (() => form . id ? '编辑权限' : '新增权限' )
2026-08-01 12:17:33 +08:00
const requiredPermission = computed (() => form . id ? 'platform:permission:edit' : 'platform:permission:create' )
2026-07-30 17:59:43 +08:00
const { captureBaseline } = useUnsavedWorkRegistration ({
key : 'permission-editor' ,
label : editorLabel ,
isOpen : editorOpen ,
model : form ,
requiredPermission ,
})
2026-08-13 11:57:44 +08:00
const availablePermissionTypes : PermissionType [] = [ '菜单' , '页面' , '按钮' , '接口' ]
2026-07-31 15:34:35 +08:00
const permissions = computed (() => serverPermissions . value )
2026-07-30 17:59:43 +08:00
const canCreate = computed (() => access . can ( 'platform:permission:create' ))
2026-08-01 12:17:33 +08:00
const canEdit = computed (() => access . can ( 'platform:permission:edit' ))
const canChangeStatus = computed (() => access . can ( 'platform:permission:status' ))
const canDelete = computed (() => access . can ( 'platform:permission:delete' ))
2026-07-30 17:59:43 +08:00
function matches ( item : Permission ) {
2026-08-01 12:17:33 +08:00
const keyword = appliedFilters . value . keyword . trim (). toLowerCase ()
return ( ! keyword || [ item . name , item . code , item . path ]. some (( value ) => value . toLowerCase (). includes ( keyword ))) && ( ! appliedFilters . value . type || item . type === appliedFilters . value . type ) && ( ! appliedFilters . value . status || item . status === appliedFilters . value . status )
2026-07-30 17:59:43 +08:00
}
const treeRows = computed (() => buildPermissionTree ( permissions . value , matches ))
2026-08-01 12:17:33 +08:00
const pagedTreeRows = computed (() => treeRows . value . slice (( page . value - 1 ) * pageSize . value , page . value * pageSize . value ))
const rootRowKeys = computed (() => treeRows . value . map (( item ) => item . id ))
2026-07-30 17:59:43 +08:00
const blockedParentIds = computed (() => new Set (
form . id ? [ form . id , ... getDescendantIds ( permissions . value , form . id )] : [],
))
const parentOptions = computed (() => permissions . value . filter (( item ) => (
2026-08-13 11:57:44 +08:00
[ '目录' , '菜单' , '页面' ]. includes ( item . type ) && ! blockedParentIds . value . has ( item . id )
2026-07-30 17:59:43 +08:00
)))
2026-08-01 12:17:33 +08:00
const permissionColumns : DataTableColumn [] = [
{ key : 'actions' , label : '操作' , width : 280 , fixed : 'left' , slotName : 'actions' },
{ key : 'name' , label : '权限名称' , minWidth : 260 , slotName : 'name' },
2026-08-17 19:52:04 +08:00
{ key : 'code' , label : '权限编码' , minWidth : 220 , slotName : 'code' },
2026-08-06 19:03:13 +08:00
{ key : 'type' , prop : 'type' , label : '类型' , width : 80 },
2026-08-17 19:52:04 +08:00
{ key : 'endpoint' , label : '路由 / 接口' , minWidth : 260 , slotName : 'endpoint' },
{ key : 'sort' , prop : 'sort' , label : '排序' , width : 72 , align : 'right' },
2026-08-01 12:17:33 +08:00
{ key : 'status' , label : '状态' , width : 88 , slotName : 'status' },
]
2026-07-30 17:59:43 +08:00
async function loadPermissions () {
loading . value = true
loadError . value = null
serverPermissions . value = []
try {
serverPermissions . value = ( await listPlatformPermissions ()). map ( mapPlatformPermission )
2026-08-01 12:17:33 +08:00
expandedRowKeys . value = rootRowKeys . value
tableRenderKey . value += 1
2026-07-30 17:59:43 +08:00
} catch ( error ) {
loadError . value = getApiErrorDetails ( error , '权限资源加载失败,请稍后重试' )
} finally {
loading . value = false
}
}
2026-08-01 12:17:33 +08:00
function applyFilters () {
appliedFilters . value = { ... filters . value }
page . value = 1
void loadPermissions ()
}
function collectDescendantIds ( nodes : PermissionTreeNode [], ids : string [] = []) {
nodes . forEach (( node ) => {
ids . push ( node . id )
if ( node . children ) collectDescendantIds ( node . children , ids )
})
return ids
}
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 ))
2026-08-13 11:57:44 +08:00
const menuCount = descendants . filter (( item ) => [ '菜单' , '页面' ]. includes ( item . type )). length
2026-08-01 12:17:33 +08:00
const actionCount = descendants . filter (( item ) => [ '按钮' , '接口' ]. includes ( item . type )). length
if ( menuCount === 0 && actionCount === 0 ) return '无下级权限'
return [ menuCount ? ` ${ menuCount } 个菜单` : '' , actionCount ? ` ${ actionCount } 项操作` : '' ]. filter ( Boolean ). join ( ' · ' )
}
function expandAllRows () {
expandedRowKeys . value = collectDescendantIds ( treeRows . value )
tableRenderKey . value += 1
}
function collapseActionRows () {
expandedRowKeys . value = rootRowKeys . value
tableRenderKey . value += 1
}
function openCreate ( parentId : string | null = null ) {
Object . assign ( form , emptyForm (), { parentId })
captureBaseline ()
editorOpen . value = true
nextTick (() => formRef . value ? . clearValidate ())
}
function openEdit ( row : Permission ) {
if ( ! canEdit . value || row . builtIn ) return
Object . assign ( form , { ... row })
captureBaseline ()
editorOpen . value = true
nextTick (() => formRef . value ? . clearValidate ())
}
function handleTypeChange () {
2026-08-13 11:57:44 +08:00
if ( ! [ '菜单' , '页面' ]. includes ( form . type )) form . path = ''
form . method = form . type === '接口' ? ( form . method === '-' ? 'GET' : form . method ) : '-'
nextTick (() => formRef . value ? . clearValidate ([ 'parentId' , 'path' , 'method' ]))
2026-08-01 12:17:33 +08:00
}
2026-07-30 17:59:43 +08:00
async function savePermission () {
2026-08-01 12:17:33 +08:00
const valid = await formRef . value ? . validate (). catch (() => false )
if ( ! valid ) return
2026-07-30 17:59:43 +08:00
if ( permissions . value . some (( item ) => item . code . toLowerCase () === form . code . toLowerCase () && item . id !== form . id )) { ElMessage . error ( '权限编码已经存在' ); return }
2026-07-31 15:34:35 +08:00
saving . value = true
try {
2026-08-01 12:17:33 +08:00
if ( form . id ) {
const updated = await updatePlatformPermission ( form . id , {
name : form . name . trim (),
parentId : form . parentId ,
2026-08-13 11:57:44 +08:00
routePath : [ '菜单' , '页面' ]. includes ( form . type ) ? form . path . trim () : null ,
2026-08-01 12:17:33 +08:00
httpMethod : form . method === '-' ? null : form . method ,
sortNo : form . sort ,
})
const index = serverPermissions . value . findIndex (( item ) => item . id === form . id )
if ( index >= 0 ) serverPermissions . value [ index ] = mapPlatformPermission ( updated )
ElMessage . success ( '权限资源已更新' )
} else {
2026-07-30 17:59:43 +08:00
const created = await createPlatformPermission ({
2026-08-13 11:57:44 +08:00
type : ({ 目录 : 'MENU' , 菜单 : 'MENU' , 页面 : 'PAGE' , 按钮 : 'BUTTON' , 接口 : 'API' } as const )[ form . type ],
2026-07-30 17:59:43 +08:00
code : form . code . trim (),
name : form . name . trim (),
2026-07-30 21:23:39 +08:00
parentId : form . parentId || '0' ,
2026-08-13 11:57:44 +08:00
routePath : [ '菜单' , '页面' ]. includes ( form . type ) ? form . path . trim () : null ,
httpMethod : form . type === '接口' ? form . method : null ,
2026-07-30 17:59:43 +08:00
sortNo : form . sort ,
})
serverPermissions . value . push ( mapPlatformPermission ( created ))
ElMessage . success ( '权限资源已创建' )
2026-08-01 12:17:33 +08:00
}
editorOpen . value = false
2026-07-31 15:34:35 +08:00
} catch ( error ) {
2026-08-01 12:17:33 +08:00
ElMessage . error ( getApiErrorMessage ( error , form . id ? '权限资源更新失败,请稍后重试' : '权限资源创建失败,请稍后重试' ))
2026-07-31 15:34:35 +08:00
} finally {
saving . value = false
2026-07-30 17:59:43 +08:00
}
}
2026-08-01 12:17:33 +08:00
async function toggleStatus ( row : Permission ) {
if ( row . builtIn ) return
const target = row . status === '启用' ? '停用' : '启用'
try {
await ElMessageBox . confirm ( `确认 ${ target } 权限“ ${ row . name } ”吗?` , '权限状态' , { confirmButtonText : '确认' , cancelButtonText : '取消' })
rowActionId . value = row . id
const updated = await changePlatformPermissionStatus ( row . id , target === '启用' ? 'ACTIVE' : 'DISABLED' )
Object . assign ( row , mapPlatformPermission ( updated ))
ElMessage . success ( `权限已 ${ target } ` )
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return
ElMessage . error ( getApiErrorMessage ( error , '权限状态更新失败,请稍后重试' ))
} finally {
rowActionId . value = ''
}
}
async function removePermission ( row : Permission ) {
if ( row . builtIn ) return
try {
await ElMessageBox . confirm ( `确认删除权限“ ${ row . name } ”吗?删除前需确保没有下级资源且未分配给角色。` , '删除权限' , { confirmButtonText : '删除' , cancelButtonText : '取消' , type : 'warning' })
rowActionId . value = row . id
await deletePlatformPermission ( row . id )
await loadPermissions ()
ElMessage . success ( '权限资源已删除' )
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return
ElMessage . error ( getApiErrorMessage ( error , '权限资源删除失败,请稍后重试' ))
} finally {
rowActionId . value = ''
}
}
2026-08-13 11:57:44 +08:00
function typeClass ( type : PermissionType ) { return { '目录' : 'directory' , '菜单' : 'menu' , '页面' : 'menu' , '按钮' : 'action' , '接口' : 'api' }[ type ] }
2026-07-30 17:59:43 +08:00
2026-08-10 17:42:17 +08:00
function canAddChild ( row : Permission ) {
2026-08-13 11:57:44 +08:00
return canCreate . value && [ '菜单' , '页面' ]. includes ( row . type )
2026-08-10 17:42:17 +08:00
}
function canEditRow ( row : Permission ) {
return canEdit . value && ! row . builtIn
}
function canChangeStatusRow ( row : Permission ) {
return canChangeStatus . value && ! row . builtIn
}
function canDeleteRow ( row : Permission ) {
return canDelete . value && ! row . builtIn
}
function permissionActionCount ( row : Permission ) {
return Number ( canAddChild ( row ))
+ Number ( canEditRow ( row ))
+ Number ( canChangeStatusRow ( row ))
+ Number ( canDeleteRow ( row ))
}
function showDeleteDirect ( row : Permission ) {
return canDeleteRow ( row ) && permissionActionCount ( row ) <= 3
}
function showDeleteMore ( row : Permission ) {
return canDeleteRow ( row ) && permissionActionCount ( row ) > 3
}
function moreActionsLabel ( row : Permission ) {
2026-08-12 12:19:02 +08:00
const resourceLabel = [ row . name ]
2026-08-10 17:42:17 +08:00
. find (( value ) => typeof value === 'string' && value . trim ())
? . trim () || '当前权限'
return ` ${ resourceLabel } 的更多操作`
}
2026-07-30 17:59:43 +08:00
onMounted ( loadPermissions )
</ script >
< template >
2026-08-10 15:36:53 +08:00
< AdminPage as = "div" >
< ListPageLayout aria -label =" 平台权限资源管理 " >
< template # toolbar >< QueryToolbar v-model = "filters" class="surface-panel" ariaLabel="权限筛选" :fields="queryFields" preference-key="permissions-query" @search="applyFilters" /></ template >
< template v-if = "loadError" # state >
< LoadErrorState title = "权限资源暂时无法加载" :message = "loadError.message" :loading = "loading" @retry ="loadPermissions" >
< template # details >< ApiErrorMeta :details = "loadError" /></ template >
</ LoadErrorState >
</ template >
2026-07-30 17:59:43 +08:00
2026-08-10 16:24:54 +08:00
< section v-if = "!loadError" class="surface-panel table-panel" >
2026-08-12 14:08:33 +08:00
< DataTable :key = "tableRenderKey" v -model:page = "page" v -model:page-size = "pageSize" :data = "pagedTreeRows" :columns = "permissionColumns" preference -key =" permissions -table " :loading = "loading" :total = "treeRows.length" row -key =" id " aria -label =" 平台权限资源列表 " :expand-row-keys = "expandedRowKeys" : tree -props =" { children : ' children ' } " empty -text =" 没有符合条件的权限资源 " >
2026-08-10 18:21:40 +08:00
< template # toolbar -start >< div class = "permission-tree-tools" >< div class = "legend-list" >< span >< i class = "is-menu" /> 目录 / 菜单 </ span >< span >< i class = "is-action" /> 操作 </ span >< span >< i class = "is-api" /> 接口预留 </ span >< span class = "permission-protection-hint" > 受保护资源不可编辑 、 停用或删除 </ span ></ div >< div class = "tree-view-actions" >< el-button link type = "primary" @click ="expandAllRows" > 展开全部 </ el -button >< el-button link @click ="collapseActionRows" > 收起操作权限 </ el -button ></ div ></ div ></ template >
2026-08-10 17:42:17 +08:00
< template # toolbar -actions >< el-button v-if = "canCreate" type="primary" @click="openCreate(null)"><Plus :size="16" /> 新增权限 </ el-button ></ template >
< template # actions = "{ row }" >
< div v-if = "permissionActionCount(row as Permission) > 0 || (row as Permission).builtIn" class="table-actions" >
2026-08-12 17:45:49 +08:00
< el-button v-if = "canAddChild(row as Permission)" size="small" plain type="primary" @click="openCreate((row as Permission).id)"><Plus :size="15" /> 添加下级 </ el-button >
< el-button v-if = "canEditRow(row as Permission)" size="small" plain type="primary" :disabled="rowActionId === (row as Permission).id" @click="openEdit(row as Permission)"><Pencil :size="15" /> 编辑 </ el-button >
< el-button v-if = "canChangeStatusRow(row as Permission)" size="small" plain :type="(row as Permission).status === '启用' ? 'danger' : 'success'" :loading="rowActionId === (row as Permission).id" @click="toggleStatus(row as Permission)"><Power :size="15" />{{ ( row as Permission ). status === '启用' ? '停用' : '启用' }}</ el-button >
< el-button v-if = "showDeleteDirect(row as Permission)" size="small" plain type="danger" :disabled="rowActionId === (row as Permission).id" @click="removePermission(row as Permission)"><Trash2 :size="15" /> 删除 </ el-button >
2026-08-10 17:42:17 +08:00
< el-dropdown v-if = "showDeleteMore(row as Permission)" trigger="click" @command="(command: string) => command === 'delete' && removePermission(row as Permission)" >
2026-08-12 17:45:49 +08:00
< el-button size = "small" plain : aria -label =" moreActionsLabel ( row as Permission )" : title = "moreActionsLabel(row as Permission)" >< MoreHorizontal :size = "16" /></ el-button >
2026-08-10 17:42:17 +08:00
< template # dropdown >< el-dropdown-menu >< el-dropdown-item command = "delete" : disabled = "rowActionId === (row as Permission).id" class = "danger-menu-item" >< Trash2 :size = "15" /> 删除 </ el-dropdown-item ></ el-dropdown-menu ></ template >
</ el-dropdown >
</ div >
< span v-else class = "secondary-text" > 仅查看 </ span >
</ template >
2026-08-13 11:57:44 +08:00
< 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 >
2026-08-01 12:17:33 +08:00
< 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 >
2026-08-10 16:24:54 +08:00
< template # pagination -left =" { total : tableTotal } " >< span > 共 {{ tableTotal }} 项 </ span ></ template >
2026-08-01 12:17:33 +08:00
</ DataTable >
2026-08-10 15:36:53 +08:00
</ section >
</ ListPageLayout >
2026-07-30 17:59:43 +08:00
2026-08-10 15:36:53 +08:00
< AppDrawer v-model = "editorOpen" :title="form.id ? '编辑权限资源' : '新增权限资源'" size="600px" class="business-drawer editor-drawer" :modal="false" :confirm-loading="saving" :confirm-text="form.id ? '保存修改' : '保存权限'" :show-confirm="form.id ? canEdit : canCreate" @confirm="savePermission" >
2026-08-01 12:17:33 +08:00
< el-form ref = "formRef" :model = "form" :rules = "formRules" label -position =" top " class = "drawer-form" >
2026-08-10 15:36:53 +08:00
< FormSection title = "资源定义" description = "定义权限名称、编码、类型和层级关系。" >
2026-07-30 17:59:43 +08:00
< div class = "form-grid two-cols" >
2026-08-01 12:17:33 +08:00
< 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 >
2026-08-13 11:57:44 +08:00
< 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 >
2026-09-03 19:29:38 +08:00
< el-form-item label = "显示排序" prop = "sort" required >< el-input-number v-model = "form.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序值" /></ el-form-item >
2026-07-30 17:59:43 +08:00
</ div >
2026-08-10 15:36:53 +08:00
</ FormSection >
2026-08-13 11:57:44 +08:00
< div class = "form-callout" >< Braces :size = "18" />< div >< strong > 权限编码约定 </ strong >< p > 使用稳定的 “ 业务域 : 资源 : 动作 ” 编码 ; 资源类型与后端 MENU 、 PAGE 、 BUTTON 、 API 契约一致 。 </ p ></ div ></ div >
2026-07-30 17:59:43 +08:00
</ el-form >
2026-08-10 15:36:53 +08:00
</ AppDrawer >
</ AdminPage >
2026-07-30 17:59:43 +08:00
</ template >
2026-08-01 12:17:33 +08:00
< style scoped >
. permission - tree - tools { display : flex ; align - items : center ; gap : 16 px ; }
2026-08-10 18:21:40 +08:00
. permission - protection - hint { color : var ( -- cc - muted ); }
2026-08-01 12:17:33 +08:00
. tree - view - actions { display : flex ; align - items : center ; gap : 4 px ; white - space : nowrap ; }
. tree - view - actions . el - button + . el - button { margin - left : 0 ; }
. permission - name > div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 2 px ; }
. permission - name small { color : var ( -- cc - muted ); font - size : 11 px ; line - height : 1.35 ; }
2026-08-10 17:42:17 +08:00
: deep (. danger - menu - item : not (. is - disabled )) { color : var ( -- el - color - danger ); }
2026-08-01 12:17:33 +08:00
@ media ( max - width : 720 px ) {
. permission - tree - tools { width : 100 % ; align - items : flex - start ; flex - direction : column ; gap : 8 px ; }
}
@ media ( max - width : 420 px ) {
. tree - view - actions { width : 100 % ; justify - content : flex - start ; }
}
</ style >