@@ -0,0 +1,312 @@
< template >
< div class = "tenant-org-binding-page" >
< section class = "page-head" >
< div >
< div class = "platform-label" > 平台控制台 < / div >
< h1 > 租户与组织绑定 < / h1 >
< p > 由平台超级管理员维护租户的数据边界与外部组织范围 ; 租户只可在已授予的独占范围内刷新组织快照 。 < / p >
< / div >
< div class = "head-actions" >
< el-button :icon = "Refresh" :loading = "directoryRefreshing" @click ="refreshDirectory" > 刷新组织目录 < / el -button >
< el-button type = "primary" :icon = "Plus" @click ="openBindingDialog()" > 新建绑定 < / el -button >
< / div >
< / section >
< section class = "metric-grid" >
< article class = "metric-card metric-card--blue" > < span > 有效租户 < / span > < strong > { { overview ? . activeTenantCount ? ? 0 } } < / strong > < small > 当前可管理的租户主体 < / small > < / article >
< article class = "metric-card metric-card--green" > < span > 已绑定 < / span > < strong > { { overview ? . boundTenantCount ? ? 0 } } < / strong > < small > 已建立生效组织范围 < / small > < / article >
< article class = "metric-card metric-card--amber" > < span > 待绑定 < / span > < strong > { { overview ? . pendingTenantCount ? ? 0 } } < / strong > < small > 尚不可执行组织同步 < / small > < / article >
< article class = "metric-card metric-card--red" > < span > 范围冲突 < / span > < strong > { { overview ? . conflictCount ? ? 0 } } < / strong > < small > 独占范围不能重叠 < / small > < / article >
< / section >
< section class = "workspace-grid" >
< article class = "panel tenant-panel" >
< div class = "panel-title" > < div > < h2 > 租户列表 < / h2 > < p > 选择租户查看其受控组织边界 < / p > < / div > < / div >
< el-input v-model = "tenantKeyword" :prefix-icon="Search" clearable placeholder="搜索租户" class="tenant-search" / >
< el-table v-loading = "loading" :data="filteredBindings" height="510" highlight-current-row @current-change="selectBinding" >
< el -table -column prop = "tenantName" label = "租户" min -width = " 140 " show -overflow -tooltip / >
< el-table-column label = "状态" width = "94" >
< template # default = "{ row }" > < el-tag size = "small" :type = "statusType(row.status)" > { { statusLabel ( row . status ) } } < / el-tag > < / template >
< / el-table-column >
< el-table-column label = "组织范围" min -width = " 140 " show -overflow -tooltip >
< template # default = "{ row }" > { { row . rootName || '—' } } < / template >
< / el-table-column >
< el-table-column label = "操作" width = "62" align = "right" >
< template # default = "{ row }" > < el-button link type = "primary" @click.stop ="openBindingDialog(row)" > {{ row.id ? ' 调整 ' : ' 绑定 ' }} < / el -button > < / template >
< / el-table-column >
< / el-table >
< / article >
< article class = "panel tree-panel" >
< div class = "panel-title" > < div > < h2 > 绑定范围 < / h2 > < p > { { selectedBinding ? . rootName || '选择租户后查看组织范围' } } < / p > < / div > < el-tag v-if = "selectedBinding?.mode" :type="selectedBinding.mode === 'EXCLUSIVE' ? 'success' : 'info'" > {{ modeLabel ( selectedBinding.mode ) }} < / el -tag > < / div >
< el-alert v-if = "selectedBinding?.mode === 'EXCLUSIVE'" type="success" :closable="false" show-icon title="该租户可在服务端限定的独占范围内刷新组织快照" / >
< el-alert v-else-if = "selectedBinding?.mode === 'AGGREGATE_ONLY'" type="info" :closable="false" show-icon title="仅汇总范围不保存人员明细,且没有组织同步入口" / >
< el-empty v-if = "!directoryTree?.roots?.length" description="尚未加载组织目录,请由平台超级管理员刷新" / >
< el-tree v-else :data = "directoryTree.roots" node -key = " externalId " :props = "treeProps" : default -expanded -keys = " selectedBinding ? .rootExternalId ? [ selectedBinding.rootExternalId ] : [ ] " highlight -current :expand-on-click-node = "false" >
< template # default = "{ data }" > < span class = "tree-node" > < span class = "tree-node__name" > { { data . name } } < / span > < small > { { nodeTypeLabel ( data . nodeType ) } } < / small > < / span > < / template >
< / el-tree >
< div class = "tree-footnote" > 项目节点用于展示集团层级 ; 当前开放组织 API 只支持集团 、 公司和部门作为可同步边界 。 < / div >
< / article >
< aside class = "governance-column" >
< article class = "panel governance-panel" >
< div class = "panel-title" > < div > < h2 > 治理与审计 < / h2 > < p > 当前操作角色 < / p > < / div > < / div >
< el-tag type = "primary" effect = "plain" > < el-icon > < Lock / > < / el-icon > 平台超级管理员 < / el-tag >
< ul class = "audit-list" >
< li > < el-icon color = "#22a06b" > < CircleCheck / > < / el-icon > < span > 绑定新建 、 调整 、 停用和目录刷新均进入系统操作日志 。 < / span > < / li >
< li > < el-icon color = "#1677ff" > < InfoFilled / > < / el-icon > < span > 范围预检由服务端重新读取目录 , 不采信浏览器传入的范围参数 。 < / span > < / li >
< li > < el-icon color = "#d97706" > < WarningFilled / > < / el-icon > < span > 独占范围与祖先 、 子孙或相同节点重叠时禁止生效 。 < / span > < / li >
< / ul >
< el-button v-if = "selectedBinding?.id && selectedBinding.status === 'ACTIVE'" plain class="disable-button" @click="disableBinding" > 停用当前绑定 < / el -button >
< / article >
< article v-if = "selectedBinding?.id && selectedBinding.mode === 'AGGREGATE_ONLY'" class="panel aggregate-panel" >
< div class = "panel-title" > < div > < h2 > 同步概览 < / h2 > < p > 仅授权的聚合指标 < / p > < / div > < el-button link type = "primary" :loading = "aggregateLoading" @click ="loadAggregate" > 刷新 < / el -button > < / div >
< div class = "aggregate-values" > < div > < strong > { { aggregate ? . coveredTenantCount ? ? 0 } } < / strong > < span > 覆盖独占租户 < / span > < / div > < div > < strong > { { aggregate ? . syncedPersonCount ? ? 0 } } < / strong > < span > 已同步人员合计 < / span > < / div > < / div >
< / article >
< / aside >
< / section >
< el-dialog v-model = "dialogVisible" :title="editingBindingId ? '调整租户组织绑定' : '新建租户组织绑定'" width="760px" destroy-on-close @closed="resetDialog" >
< el -steps : active = "precheckResult ? 3 : 2" finish -status = " success " align -center class = "binding-steps" > < el-step title = "选择租户" / > < el-step title = "选择组织范围" / > < el-step title = "冲突预检" / > < / el-steps >
< el-form label -position = " top " class = "binding-form" >
< div class = "form-grid" >
< el-form-item label = "目标租户" required >
< el-select v-model = "bindingForm.tenantId" filterable placeholder="请选择租户" :disabled="Boolean(editingBindingId)" >
< el -option v-for = "item in tenantOptions" :key="item.tenantId" :label="item.tenantName" :value="item.tenantId" / >
< / el-select >
< / el-form-item >
< el-form-item label = "范围模式" required >
< el-radio-group v-model = "bindingForm.mode"><el-radio-button label="EXCLUSIVE">独占范围</el-radio-button><el-radio-button label="AGGREGATE_ONLY" > 仅汇总 < / el -radio -button > < / el-radio-group >
< / el-form-item >
< / div >
< el-form-item label = "组织根节点" required >
< el-alert v-if = "!directoryTree?.roots?.length" type="warning" :closable="false" title="尚无组织目录,请先关闭窗口并刷新组织目录。" / >
< el-tree v-else :data = "dialogDirectoryNodes" node -key = " externalId " :props = "dialogTreeProps" highlight -current :expand-on-click-node = "false" @ node -click = " selectBindingRoot " >
< template # default = "{ data }" > < span class = "dialog-tree-node" > < span > { { data . name } } < / span > < el-tag v-if = "!data.bindable" size="small" type="info" > 仅展示 < / el -tag > < small > { { nodeTypeLabel ( data . nodeType ) } } < / small > < / span > < / template >
< / el-tree >
< div v-if = "bindingForm.rootExternalId" class="selected-root" > 已选择 : {{ selectedDialogRoot ? .name | | bindingForm.rootExternalId }} < / div >
< / el -form -item >
< / el-form >
< section v-if = "precheckResult" class="precheck-result" :class="{ 'precheck-result--conflict': !precheckResult.canActivate }" >
< div class = "precheck-title" > < el-icon > < CircleCheck v-if = "precheckResult.canActivate" / > < WarningFilled v-else / > < / el-icon > < strong > { { precheckResult . canActivate ? '范围预检通过,可以生效绑定' : ` 发现 ${ precheckResult . conflicts . length } 项独占范围冲突 ` } } < / strong > < / div >
< el-table v-if = "precheckResult.conflicts.length" :data="precheckResult.conflicts" size="small"><el-table-column prop="tenantName" label="已绑定租户" /><el-table-column prop="rootName" label="冲突组织" /><el-table-column prop="reason" label="冲突原因" min-width="240" / > < / el-table >
< div v-if = "!precheckResult.canActivate && bindingForm.mode === 'EXCLUSIVE'" class="resolution-card"><div><strong>推荐:保留子公司独占租户,集团改为仅汇总</strong><p>子公司保持人员与业务数据隔离;集团仅查看授权聚合指标,不能同步或读取子公司明细。</p></div><el-button type="primary" plain @click="applyAggregateRecommendation" > 采用仅汇总方案 < / el -button > < / div >
< / section >
< template # footer >
< el-button @click ="dialogVisible = false" > 取消 < / el -button >
< el-button :loading = "prechecking" : disabled = "!bindingForm.tenantId || !bindingForm.rootExternalId" @click ="precheck" > 执行预检 < / el -button >
< el-button type = "primary" :loading = "saving" :disabled = "!precheckResult?.canActivate" @click ="saveBinding" > 确认绑定 < / el -button >
< / template >
< / el-dialog >
< / div >
< / template >
< script setup name = "TenantOrgBinding" lang = "ts" >
import { CircleCheck , InfoFilled , Lock , Plus , Refresh , Search , WarningFilled } from '@element-plus/icons-vue' ;
import { ElMessage , ElMessageBox } from 'element-plus' ;
import { computed , onMounted , reactive , ref } from 'vue' ;
import {
createTenantOrgBinding ,
getOrgDirectoryTree ,
getTenantOrgAggregate ,
listTenantOrgBindings ,
precheckTenantOrgBinding ,
refreshOrgDirectory ,
updateTenantOrgBinding ,
type AggregateView ,
type BindingPrecheck ,
type OrgDirectoryNode ,
type OrgDirectoryTree ,
type TenantOrgBinding ,
type TenantOrgBindingOverview
} from '@/api/aihr/tenant-org-governance' ;
type DialogNode = OrgDirectoryNode & { disabled : boolean ; children : DialogNode [ ] } ;
const loading = ref ( false ) ;
const directoryRefreshing = ref ( false ) ;
const overview = ref < TenantOrgBindingOverview > ( ) ;
const directoryTree = ref < OrgDirectoryTree > ( ) ;
const selectedBinding = ref < TenantOrgBinding > ( ) ;
const tenantKeyword = ref ( '' ) ;
const aggregate = ref < AggregateView > ( ) ;
const aggregateLoading = ref ( false ) ;
const dialogVisible = ref ( false ) ;
const editingBindingId = ref < number > ( ) ;
const prechecking = ref ( false ) ;
const saving = ref ( false ) ;
const precheckResult = ref < BindingPrecheck > ( ) ;
const bindingForm = reactive ( { tenantId : '' , rootExternalId : '' , mode : 'EXCLUSIVE' as 'EXCLUSIVE' | 'AGGREGATE_ONLY' } ) ;
const treeProps = { label : 'name' , children : 'children' } ;
const dialogTreeProps = { label : 'name' , children : 'children' , disabled : 'disabled' } ;
const filteredBindings = computed ( ( ) => {
const keyword = tenantKeyword . value . trim ( ) . toLowerCase ( ) ;
return ( overview . value ? . bindings || [ ] ) . filter ( ( item ) => ! keyword || ` ${ item . tenantName } ${ item . tenantId } ` . toLowerCase ( ) . includes ( keyword ) ) ;
} ) ;
const tenantOptions = computed ( ( ) => overview . value ? . bindings || [ ] ) ;
const dialogDirectoryNodes = computed < DialogNode [ ] > ( ( ) => markDisabled ( directoryTree . value ? . roots || [ ] ) ) ;
const selectedDialogRoot = computed ( ( ) => findNode ( directoryTree . value ? . roots || [ ] , bindingForm . rootExternalId ) ) ;
const statusLabel = ( status : TenantOrgBinding [ 'status' ] ) => ( { UNBOUND : '待绑定' , DRAFT : '草稿' , ACTIVE : '已绑定' , DISABLED : '已停用' } ) [ status ] ;
const statusType = ( status : TenantOrgBinding [ 'status' ] ) => ( { UNBOUND : 'warning' , DRAFT : 'info' , ACTIVE : 'success' , DISABLED : 'info' } ) [ status ] as 'success' | 'warning' | 'info' ;
const modeLabel = ( mode ? : string ) => mode === 'EXCLUSIVE' ? '独占范围' : '仅汇总' ;
const nodeTypeLabel = ( type : string ) => ( { GROUP : '集团' , COMPANY : '公司' , PROJECT : '项目' , DEPARTMENT : '部门' } ) [ type ] || type ;
const load = async ( ) => {
loading . value = true ;
try {
const [ tree , bindingList ] = await Promise . all ( [ getOrgDirectoryTree ( ) , listTenantOrgBindings ( ) ] ) ;
directoryTree . value = tree . data ;
overview . value = bindingList . data ;
const current = selectedBinding . value && bindingList . data . bindings . find ( ( item ) => item . tenantId === selectedBinding . value ? . tenantId ) ;
selectedBinding . value = current || bindingList . data . bindings [ 0 ] ;
if ( selectedBinding . value ? . mode === 'AGGREGATE_ONLY' ) await loadAggregate ( ) ;
} catch {
ElMessage . error ( '租户组织治理数据加载失败,请确认迁移已执行且当前账号为平台超级管理员' ) ;
} finally {
loading . value = false ;
}
} ;
const refreshDirectory = async ( ) => {
directoryRefreshing . value = true ;
try {
const result = await refreshOrgDirectory ( ) ;
ElMessage . success ( ` 组织目录已刷新,共 ${ result . data . nodeCount } 个节点 ` ) ;
if ( result . data . warnings . length ) ElMessage . warning ( result . data . warnings . join ( '; ' ) ) ;
await load ( ) ;
} catch {
ElMessage . error ( '组织目录刷新失败,请检查平台连接器配置与授权范围' ) ;
} finally {
directoryRefreshing . value = false ;
}
} ;
const selectBinding = ( binding ? : TenantOrgBinding ) => {
selectedBinding . value = binding ;
aggregate . value = undefined ;
if ( binding ? . mode === 'AGGREGATE_ONLY' ) void loadAggregate ( ) ;
} ;
const openBindingDialog = ( binding ? : TenantOrgBinding ) => {
editingBindingId . value = binding ? . id ;
bindingForm . tenantId = binding ? . tenantId || '' ;
bindingForm . rootExternalId = binding ? . rootExternalId || '' ;
bindingForm . mode = binding ? . mode || 'EXCLUSIVE' ;
precheckResult . value = undefined ;
dialogVisible . value = true ;
} ;
const selectBindingRoot = ( node : DialogNode ) => {
if ( ! node . bindable ) {
ElMessage . warning ( '项目节点仅用于展示;当前开放 API 只支持集团、公司或部门作为同步边界' ) ;
return ;
}
bindingForm . rootExternalId = node . externalId ;
precheckResult . value = undefined ;
} ;
const precheck = async ( ) => {
prechecking . value = true ;
try {
const result = await precheckTenantOrgBinding ( { ... bindingForm , status : 'ACTIVE' } ) ;
precheckResult . value = result . data ;
if ( result . data . canActivate ) ElMessage . success ( '范围预检通过' ) ;
} catch {
ElMessage . error ( '范围预检失败,请刷新组织目录后重试' ) ;
} finally {
prechecking . value = false ;
}
} ;
const applyAggregateRecommendation = async ( ) => {
bindingForm . mode = 'AGGREGATE_ONLY' ;
precheckResult . value = undefined ;
await precheck ( ) ;
} ;
const saveBinding = async ( ) => {
saving . value = true ;
try {
const payload = { ... bindingForm , status : 'ACTIVE' as const } ;
const result = editingBindingId . value ? await updateTenantOrgBinding ( editingBindingId . value , payload ) : await createTenantOrgBinding ( payload ) ;
selectedBinding . value = result . data ;
ElMessage . success ( '租户组织绑定已生效' ) ;
dialogVisible . value = false ;
await load ( ) ;
} catch {
ElMessage . error ( '绑定保存失败;若范围已变化,请重新执行预检' ) ;
} finally {
saving . value = false ;
}
} ;
const disableBinding = async ( ) => {
const binding = selectedBinding . value ;
if ( ! binding ? . id || ! binding . rootExternalId || ! binding . mode ) return ;
try {
await ElMessageBox . confirm ( '停用后该租户不能再执行组织同步,是否继续?' , '停用组织绑定' , { type : 'warning' } ) ;
await updateTenantOrgBinding ( binding . id , { tenantId : binding . tenantId , rootExternalId : binding . rootExternalId , mode : binding . mode , status : 'DISABLED' } ) ;
ElMessage . success ( '绑定已停用' ) ;
await load ( ) ;
} catch ( error ) {
if ( error !== 'cancel' && error !== 'close' ) ElMessage . error ( '绑定停用失败' ) ;
}
} ;
const loadAggregate = async ( ) => {
if ( ! selectedBinding . value ? . id || selectedBinding . value . mode !== 'AGGREGATE_ONLY' ) return ;
aggregateLoading . value = true ;
try {
aggregate . value = ( await getTenantOrgAggregate ( selectedBinding . value . id ) ) . data ;
} catch {
ElMessage . error ( '汇总指标加载失败' ) ;
} finally {
aggregateLoading . value = false ;
}
} ;
const resetDialog = ( ) => {
editingBindingId . value = undefined ;
precheckResult . value = undefined ;
} ;
const markDisabled = ( nodes : OrgDirectoryNode [ ] ) : DialogNode [ ] => nodes . map ( ( node ) => ( { ... node , disabled : ! node . bindable , children : markDisabled ( node . children || [ ] ) } ) ) ;
const findNode = ( nodes : OrgDirectoryNode [ ] , id : string ) : OrgDirectoryNode | undefined => {
for ( const node of nodes ) {
if ( node . externalId === id ) return node ;
const found = findNode ( node . children || [ ] , id ) ;
if ( found ) return found ;
}
} ;
onMounted ( load ) ;
< / script >
< style scoped lang = "scss" >
. tenant - org - binding - page { min - height : 100 % ; padding : 22 px ; background : # f5f7fb ; color : # 1 d2939 ; }
. page - head , . panel , . metric - card { background : # fff ; border : 1 px solid # e7ecf3 ; border - radius : 12 px ; box - shadow : 0 5 px 18 px rgb ( 16 24 40 / 4 % ) ; }
. page - head { display : flex ; align - items : center ; justify - content : space - between ; gap : 24 px ; padding : 22 px 24 px ; }
. platform - label { display : inline - block ; padding : 2 px 8 px ; border - radius : 999 px ; color : # 175 cd3 ; background : # eff8ff ; font - size : 12 px ; font - weight : 600 ; }
h1 , h2 , p { margin : 0 ; }
h1 { margin - top : 7 px ; font - size : 25 px ; line - height : 1.2 ; }
. page - head p , . panel - title p { margin - top : 7 px ; color : # 667085 ; font - size : 13 px ; }
. head - actions { display : flex ; flex - wrap : wrap ; gap : 10 px ; }
. metric - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; gap : 14 px ; margin - top : 16 px ; }
. metric - card { position : relative ; overflow : hidden ; padding : 17 px 18 px ; }
. metric - card : : before { position : absolute ; top : 0 ; left : 0 ; width : 4 px ; height : 100 % ; background : currentcolor ; content : '' ; }
. metric - card span { display : block ; color : # 667085 ; font - size : 13 px ; }
. metric - card strong { display : block ; margin - top : 4 px ; font - size : 28 px ; line - height : 1.1 ; }
. metric - card small { display : block ; margin - top : 5 px ; color : # 98 a2b3 ; }
. metric - card -- blue { color : # 1677 ff ; } . metric - card -- green { color : # 12 b76a ; } . metric - card -- amber { color : # f79009 ; } . metric - card -- red { color : # f04438 ; }
. workspace - grid { display : grid ; grid - template - columns : minmax ( 300 px , 1.05 fr ) minmax ( 360 px , 1.25 fr ) minmax ( 260 px , .9 fr ) ; gap : 16 px ; margin - top : 16 px ; align - items : start ; }
. panel { padding : 18 px ; } . panel - title { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 12 px ; margin - bottom : 14 px ; } . panel - title h2 { font - size : 17 px ; }
. tenant - search { margin - bottom : 12 px ; } . tree - panel : deep ( . el - alert ) { margin - bottom : 12 px ; } . tree - panel : deep ( . el - tree ) { min - height : 370 px ; } . tree - node , . dialog - tree - node { display : inline - flex ; align - items : center ; gap : 8 px ; min - width : 0 ; } . tree - node _ _name { overflow : hidden ; text - overflow : ellipsis ; white - space : nowrap ; } . tree - node small , . dialog - tree - node small { color : # 98 a2b3 ; font - size : 12 px ; } . tree - footnote { margin - top : 12 px ; padding : 10 px 12 px ; border - radius : 8 px ; color : # 7 a5d00 ; background : # fff9e8 ; font - size : 12 px ; line - height : 1.6 ; }
. governance - column { display : grid ; gap : 16 px ; } . governance - panel : deep ( . el - tag . el - icon ) { margin - right : 4 px ; } . audit - list { display : grid ; gap : 14 px ; padding : 0 ; margin : 18 px 0 ; list - style : none ; } . audit - list li { display : flex ; gap : 9 px ; color : # 475467 ; font - size : 13 px ; line - height : 1.55 ; } . audit - list . el - icon { flex : 0 0 auto ; margin - top : 2 px ; } . disable - button { width : 100 % ; } . aggregate - values { display : grid ; grid - template - columns : repeat ( 2 , 1 fr ) ; gap : 10 px ; } . aggregate - values div { padding : 12 px ; border - radius : 8 px ; background : # f8fbff ; } . aggregate - values strong { display : block ; color : # 175 cd3 ; font - size : 22 px ; } . aggregate - values span { color : # 667085 ; font - size : 12 px ; }
. binding - steps { margin : 0 0 22 px ; } . binding - form : deep ( . el - form - item ) { margin - bottom : 18 px ; } . form - grid { display : grid ; grid - template - columns : 1 fr 1 fr ; gap : 14 px ; } . form - grid : deep ( . el - select ) { width : 100 % ; } . binding - form : deep ( . el - tree ) { max - height : 220 px ; padding : 10 px ; overflow : auto ; border : 1 px solid # e4e7ed ; border - radius : 8 px ; } . dialog - tree - node { width : 100 % ; justify - content : space - between ; padding - right : 10 px ; } . selected - root { margin - top : 8 px ; color : # 175 cd3 ; font - size : 13 px ; } . precheck - result { padding : 14 px ; border : 1 px solid # b7ebc6 ; border - radius : 10 px ; background : # f6ffed ; } . precheck - result -- conflict { border - color : # ffccc7 ; background : # fff2f0 ; } . precheck - title { display : flex ; align - items : center ; gap : 8 px ; margin - bottom : 12 px ; color : # 1 d6f42 ; } . precheck - result -- conflict . precheck - title { color : # cf1322 ; } . resolution - card { display : flex ; align - items : center ; justify - content : space - between ; gap : 14 px ; margin - top : 12 px ; padding : 13 px ; border : 1 px solid # 91 caff ; border - radius : 8 px ; background : # f0f7ff ; } . resolution - card p { margin - top : 5 px ; color : # 475467 ; font - size : 12 px ; line - height : 1.55 ; }
@ media ( max - width : 1280 px ) { . workspace - grid { grid - template - columns : 1 fr 1 fr ; } . governance - column { grid - column : span 2 ; grid - template - columns : 1 fr 1 fr ; } . metric - grid { grid - template - columns : repeat ( 2 , 1 fr ) ; } }
@ media ( max - width : 760 px ) { . tenant - org - binding - page { padding : 12 px ; } . page - head , . resolution - card { align - items : flex - start ; flex - direction : column ; } . workspace - grid , . governance - column , . metric - grid , . form - grid { grid - template - columns : 1 fr ; } . governance - column { grid - column : auto ; } . head - actions { width : 100 % ; } . head - actions . el - button { flex : 1 ; } . binding - steps : deep ( . el - step _ _title ) { font - size : 11 px ; } }
< / style >