刘光辉
9 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
<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>