<script lang="ts" setup>
|
interface Props {
|
identityEnabled?: boolean;
|
}
|
|
withDefaults(defineProps<Props>(), {
|
identityEnabled: false,
|
});
|
</script>
|
|
<template>
|
<section class="permission-relation-guide" aria-label="权限合并关系说明">
|
<div class="relation-head">
|
<div class="relation-legend" aria-label="关系符号">
|
<span class="legend-item"><b class="legend-symbol is-union">∪</b>并集</span>
|
<span class="legend-item"><b class="legend-symbol is-intersection">∩</b>交集</span>
|
<span class="legend-item"><b class="legend-symbol is-filter">→</b>范围过滤</span>
|
<span class="legend-item"><b class="legend-symbol is-exclusive">×</b>互斥</span>
|
</div>
|
<span class="relation-head-note">四个开关可独立组合,彼此不互斥</span>
|
</div>
|
|
<div class="relation-map">
|
<div class="source-area">
|
<div class="area-label">可能的授权来源</div>
|
<div class="source-grid">
|
<div class="relation-node source-node">
|
<strong>用户角色</strong>
|
<span>始终启用</span>
|
</div>
|
<div class="relation-node source-node">
|
<strong>组织/岗位直授权</strong>
|
<span>按配置进入候选集</span>
|
</div>
|
<div class="relation-node source-node">
|
<strong>组织/岗位角色</strong>
|
<span>按成员关系进入候选集</span>
|
</div>
|
<div class="relation-node source-node">
|
<strong>权限集合</strong>
|
<span>向成员追加权限</span>
|
</div>
|
</div>
|
</div>
|
|
<div class="merge-operator" aria-label="取并集">
|
<span>∪</span>
|
<small>去重合并</small>
|
</div>
|
|
<div class="result-flow">
|
<div class="relation-node candidate-node">
|
<strong>候选权限</strong>
|
<span>任一来源授予即可进入</span>
|
</div>
|
<div class="flow-arrow">→</div>
|
<div class="relation-node filter-node" :class="{ 'is-pass-through': !identityEnabled }">
|
<strong>{{ identityEnabled ? '当前身份范围过滤' : '身份过滤已关闭' }}</strong>
|
<span>{{ identityEnabled ? '移除非当前身份的岗位和角色来源' : '候选来源直接通过' }}</span>
|
</div>
|
<div class="flow-arrow">→</div>
|
<div class="relation-node final-node">
|
<strong>最终有效权限</strong>
|
<span>菜单、按钮、字段、流程、打印等</span>
|
</div>
|
</div>
|
</div>
|
|
<div class="relation-examples">
|
<div class="example-row">
|
<span class="example-type is-union">并集</span>
|
<div>
|
<strong>多个授权来源累加</strong>
|
<p>角色 A {查询、新增} ∪ 角色 B {查询、导出} = {查询、新增、导出}。</p>
|
</div>
|
</div>
|
<div class="example-row">
|
<span class="example-type is-filter">过滤</span>
|
<div>
|
<strong>身份筛选授权来源</strong>
|
<p>开启身份后,先移除不属于当前身份的岗位和角色,再对剩余来源取并集;不是按权限项逐项求交集。</p>
|
</div>
|
</div>
|
<div class="example-row">
|
<span class="example-type is-intersection">交集</span>
|
<div>
|
<strong>配置入口显示条件</strong>
|
<p>入口可见 = 原菜单授权 ∩ 对应开关开启。打开开关不会自动授予菜单权限。</p>
|
</div>
|
</div>
|
<div class="example-row">
|
<span class="example-type is-exclusive">互斥</span>
|
<div>
|
<strong>只约束角色或岗位分配</strong>
|
<p>例如“制单人 × 终审人”不能同时分配给同一用户;未显式配置互斥约束时,多角色仍按并集合并。</p>
|
</div>
|
</div>
|
</div>
|
|
<div class="special-rules">
|
<p><strong>数据权限:</strong>不同授权来源的数据范围按 OR 扩大可见记录;单个数据方案内部仍按方案配置的 AND/OR 计算,任一来源包含“全部数据”时结果为全部数据。</p>
|
<p><strong>组织层级:</strong>下级组织或岗位的可配置范围受上级授权及关联角色限制,这是配置边界,不是对用户最终权限做减法。</p>
|
<p><strong>管理员:</strong>平台管理员走全权限分支,不参与上述普通用户合并计算。</p>
|
<p><strong>入口关闭:</strong>组织/岗位角色、组织权限和权限集合开关只收起配置入口,不撤销已经存在的授权。</p>
|
</div>
|
</section>
|
</template>
|
|
<style lang="scss" scoped>
|
.permission-relation-guide {
|
min-width: 0;
|
color: #1f2329;
|
}
|
|
.relation-head {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12px 20px;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 14px;
|
}
|
|
.relation-legend {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 8px;
|
}
|
|
.legend-item {
|
display: inline-flex;
|
gap: 6px;
|
align-items: center;
|
min-height: 28px;
|
padding: 2px 9px;
|
font-size: 13px;
|
color: #4e5969;
|
background: #fff;
|
border: 1px solid #e5e6eb;
|
border-radius: 4px;
|
}
|
|
.legend-symbol {
|
font-size: 18px;
|
line-height: 1;
|
}
|
|
.is-union {
|
color: #1677ff;
|
}
|
|
.is-intersection {
|
color: #00a870;
|
}
|
|
.is-filter {
|
color: #d46b08;
|
}
|
|
.is-exclusive {
|
color: #d4380d;
|
}
|
|
.relation-head-note {
|
font-size: 13px;
|
color: #646a73;
|
}
|
|
.relation-map {
|
padding: 18px;
|
background: #f7f8fa;
|
border: 1px solid #e5e6eb;
|
border-radius: 6px;
|
}
|
|
.area-label {
|
margin-bottom: 10px;
|
font-size: 13px;
|
font-weight: 600;
|
color: #646a73;
|
}
|
|
.source-grid {
|
display: grid;
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
gap: 10px;
|
}
|
|
.relation-node {
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
min-width: 0;
|
min-height: 58px;
|
padding: 10px 12px;
|
background: #fff;
|
border: 1px solid #d9dfe8;
|
border-radius: 5px;
|
|
strong {
|
overflow-wrap: anywhere;
|
font-size: 14px;
|
line-height: 22px;
|
}
|
|
span {
|
margin-top: 2px;
|
overflow-wrap: anywhere;
|
font-size: 12px;
|
line-height: 18px;
|
color: #86909c;
|
}
|
}
|
|
.source-node {
|
border-top: 3px solid #4096ff;
|
}
|
|
.merge-operator {
|
display: flex;
|
gap: 8px;
|
align-items: center;
|
justify-content: center;
|
height: 46px;
|
color: #1677ff;
|
|
span {
|
font-size: 28px;
|
line-height: 1;
|
}
|
|
small {
|
font-size: 12px;
|
color: #646a73;
|
}
|
}
|
|
.result-flow {
|
display: grid;
|
grid-template-columns: minmax(140px, 1fr) 32px minmax(180px, 1.3fr) 32px minmax(150px, 1fr);
|
gap: 8px;
|
align-items: stretch;
|
}
|
|
.candidate-node {
|
border-color: #91caff;
|
}
|
|
.filter-node {
|
background: #fff7e6;
|
border-color: #ffd591;
|
}
|
|
.filter-node.is-pass-through {
|
background: #f5f5f5;
|
border-color: #d9d9d9;
|
}
|
|
.final-node {
|
background: #f0fff8;
|
border-color: #87e8c1;
|
}
|
|
.flow-arrow {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
min-width: 32px;
|
font-size: 22px;
|
color: #86909c;
|
}
|
|
.relation-examples {
|
display: grid;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 0 24px;
|
margin-top: 16px;
|
border-top: 1px solid #e5e6eb;
|
}
|
|
.example-row {
|
display: grid;
|
grid-template-columns: 48px minmax(0, 1fr);
|
gap: 10px;
|
align-items: start;
|
padding: 14px 0;
|
border-bottom: 1px solid #e5e6eb;
|
|
strong {
|
display: block;
|
margin-bottom: 3px;
|
font-size: 14px;
|
line-height: 22px;
|
}
|
|
p {
|
margin: 0;
|
font-size: 13px;
|
line-height: 21px;
|
color: #646a73;
|
}
|
}
|
|
.example-type {
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
width: 48px;
|
min-height: 24px;
|
font-size: 12px;
|
font-weight: 600;
|
background: #f2f3f5;
|
border-radius: 4px;
|
|
&.is-union {
|
color: #0958d9;
|
background: #e6f4ff;
|
}
|
|
&.is-filter {
|
color: #ad4e00;
|
background: #fff7e6;
|
}
|
|
&.is-intersection {
|
color: #087f5b;
|
background: #e8fff5;
|
}
|
|
&.is-exclusive {
|
color: #b42318;
|
background: #fff1f0;
|
}
|
}
|
|
.special-rules {
|
padding-top: 12px;
|
|
p {
|
margin: 0 0 6px;
|
font-size: 13px;
|
line-height: 21px;
|
color: #646a73;
|
}
|
|
strong {
|
color: #1f2329;
|
}
|
}
|
|
@media (max-width: 1200px) {
|
.source-grid {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
|
.result-flow {
|
grid-template-columns: minmax(0, 1fr);
|
}
|
|
.flow-arrow {
|
height: 24px;
|
transform: rotate(90deg);
|
}
|
}
|
|
@media (max-width: 768px) {
|
.source-grid,
|
.relation-examples {
|
grid-template-columns: minmax(0, 1fr);
|
}
|
}
|
</style>
|